diff --git a/config.py b/config.py
index e4e63a5..7d5f49f 100644
--- a/config.py
+++ b/config.py
@@ -27,36 +27,38 @@
# Import Theme
from libqtile import hook
from libqtile.log_utils import logger
-from kuro.utils import load_config_class
-import traceback
try:
from kuro.theme import Kuro
Theme = Kuro()
except ImportError as e:
- logger.error(traceback.format_exc())
logger.error("Could not load Kuro Theme. Trying to load BaseTheme. Error: {}".format(e))
try:
from kuro.base import BaseTheme as Kuro
Theme = Kuro()
except ImportError as e:
Kuro = None
- logger.error(traceback.format_exc())
raise ImportError("Could not load theme Config or BaseTheme! Error: {}".format(e))
# Import theme configuration
-Config = load_config_class()
-if Config is None:
- raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
+try:
+ from kuro.config import Config
+except ImportError as e:
+ logger.error("Could not load Kuro Config. Trying to load BaseConfig. Error: {}".format(e))
+ try:
+ from kuro.base import BaseConfig as Config
+ except ImportError as e:
+ Config = None
+ raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
+
try:
- logger.warning("Initializing theme...")
- logger.warning(f"Using config variables for '{Config.get('config_name', '????')}'")
+ logger.info("Initializing theme...")
# Initialize the Theme
Theme.initialize()
- logger.warning("Initialize done")
+ logger.info("Initialize done")
- logger.warning("Hooking theme into callbacks...")
+ logger.info("Hooking theme into callbacks...")
# Hook theme into all hooks we know of
hook.subscribe.startup_once(Theme.callback_startup_once)
hook.subscribe.startup(Theme.callback_startup)
@@ -80,11 +82,10 @@ try:
hook.subscribe.selection_notify(Theme.callback_selection_notify)
hook.subscribe.selection_change(Theme.callback_selection_change)
hook.subscribe.screen_change(Theme.callback_screen_change)
- hook.subscribe.screens_reconfigured(Theme.callback_screens_reconfigured)
hook.subscribe.current_screen_change(Theme.callback_current_screen_change)
- logger.warning("Hooking done")
+ logger.info("Hooking done")
- logger.warning("Initializing theme variables")
+ logger.info("Initializing theme variables")
# Initialize variables from theme
keys = Theme.keys
mouse = Theme.mouse
@@ -103,18 +104,14 @@ try:
focus_on_window_activation = Theme.focus_on_window_activation
extensions = Theme.extensions
wmname = Theme.wmname
- reconfigure_screens = Theme.reconfigure_screens
- logger.warning("Variable initialization done")
+ logger.info("Variable initialization done")
except Exception as e:
Theme = None
Config = None
- logger.error(traceback.format_exc())
raise AttributeError("Could not configure theme! Error: {}".format(e))
def main(qtile):
- Config.initialize(qtile)
-
# set logging level
if Config.get('debug', False):
if Config.get('verbose', False):
diff --git a/kuro/base.py b/kuro/base.py
index 8f02d65..221d1fd 100644
--- a/kuro/base.py
+++ b/kuro/base.py
@@ -1,38 +1,24 @@
-import time
-
from libqtile import layout as libqtile_layout, layout, bar, widget
from libqtile.lazy import lazy
from libqtile.config import Key, Group, Screen, Drag, Click, Match
-# Initialize logging
-from libqtile.log_utils import logger
-
class BaseConfig:
- config_name = "KuroBase"
-
@classmethod
def get(cls, key, default):
if hasattr(cls, key):
- return getattr(cls, key)
- #return cls.__dict__[key]
+ return cls.__dict__[key]
else:
return default
- @classmethod
- def initialize(cls, qtile):
- # Can do extra initialization based on qtile instance here
- pass
-
class BaseTheme:
# Changing variables initialized by function
keys = None
- mouse = None
groups = None
layouts = None
widget_defaults = None
- screens = []
+ screens = None
qtile = None
# 'Static' variables
@@ -55,7 +41,6 @@ class BaseTheme:
auto_fullscreen = True
focus_on_window_activation = "smart"
extensions = []
- reconfigure_screens = False
# 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
@@ -70,21 +55,12 @@ class BaseTheme:
# 'export _JAVA_AWT_WM_NONREPARENTING=1'
wmname = "LG3D"
- def __init__(self):
- self.startup_time = time.time()
-
def initialize(self):
- logger.info("Initializing widget defaults...")
self.widget_defaults = self.init_widget_defaults()
- logger.info("Initializing keys...")
self.keys = self.init_keys()
- logger.info("Initializing groups...")
self.groups = self.init_groups()
- logger.info("Initializing layouts...")
self.layouts = self.init_layouts()
- logger.info("Initializing screens...")
self.screens = self.init_screens()
- logger.info("Initializing mouse...")
self.mouse = self.init_mouse()
def init_keys(self):
@@ -119,15 +95,6 @@ class BaseTheme:
Key(["mod4"], "r", lazy.spawncmd()),
]
- def init_mouse(self):
- 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())
- ]
-
def init_groups(self):
groups = [Group(i) for i in "asdfuiop"]
for i in groups:
@@ -171,6 +138,15 @@ class BaseTheme:
),
]
+ def init_mouse(self):
+ 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
def callback_startup_once(self, *args, **kwargs):
pass
@@ -244,8 +220,5 @@ class BaseTheme:
def callback_screen_change(self, *args, **kwargs):
pass
- def callback_screens_reconfigured(self, *args, **kwargs):
- pass
-
def callback_current_screen_change(self, *args, **kwargs):
pass
diff --git a/kuro/config/__init__.py b/kuro/config.py
similarity index 69%
rename from kuro/config/__init__.py
rename to kuro/config.py
index 00ff7ea..5bc2ce7 100644
--- a/kuro/config/__init__.py
+++ b/kuro/config.py
@@ -1,11 +1,8 @@
from kuro.base import BaseConfig
-from libqtile.log_utils import logger
# Config variables used in the main configuration
class Config(BaseConfig):
- config_name = "KuroGeneral"
-
# Show debug bar and messages
debug = False
verbose = False
@@ -17,48 +14,22 @@ class Config(BaseConfig):
inactive_light = "#777777"
inactive_dark = "#333333"
- # Predefined commands
- cmd_brightness_up = "sudo /usr/bin/xbacklight -inc 10"
- cmd_brightness_down = "sudo /usr/bin/xbacklight -dec 10"
- cmd_screenshot = "/home/kevin/bin/screenshot.sh"
- cmd_alt_screenshot = "/home/kevin/bin/screenshot.sh"
- lock_command = "bash /home/kevin/bin/lock.sh"
- cliphistory_command = "/home/kevin/bin/cliphistory.sh"
-
# Default Applications
app_terminal = "ghostty"
- app_launcher = "wofi --show run,drun"
- file_manager = "thunar"
- visualizer_app = "glava"
+ app_terminal_init = "ghostty --gtk-single-instance=true --quit-after-last-window-close=false --initial-window=true"
+ app_launcher = "/home/kevin/bin/dmenu_wal.sh"
web_browser = "firefox"
-
- # Autostart applications
- apps_autostart_group = [
- {'group': "", 'command': ["firefox"]},
- {'group': "", 'command': ["ghostty", "--gtk-single-instance=true", "--quit-after-last-window-close=false", "--initial-window=true"]},
- {'group': "", 'command': ["/usr/bin/rambox"]},
- {'group': "", 'command': ["thunar"]},
- {'group': "", 'command': ["thunderbird"]},
- {'group': "", 'command': ["spotify"]},
- ]
- apps_autostart = {
- 'common': [
- ["/usr/lib/kdeconnectd"], # KDE Connect daemon
- ["kdeconnect-indicator"], # KDE Connect tray
- ["vorta"], # Vorta backup scheduler
- ],
- 'x11': [
- ["dunst"], # Notification daemon
- ["picom", "-b"], # Compositor
- ["xfce4-clipman"], # Clipboard manager
- ["xiccd"], # Color profile manager
- ],
- 'wayland': [
- ["mako"], # Notification daemon
- ["wl-paste", "--watch", "cliphist", "store"], # Clipboard manager
- ["kanshi"], # Display hotplugging
- ]
- }
+ file_manager = "thunar"
+ app_chat = "/usr/bin/rambox"
+ app_irc = ""
+ app_mail = "thunderbird"
+ app_music = "spotify"
+ cmd_brightness_up = "sudo /usr/bin/xbacklight -inc 10"
+ cmd_brightness_down = "sudo /usr/bin/xbacklight -dec 10"
+ lock_command = "bash /home/kevin/bin/lock.sh"
+ visualizer_app = "glava"
+ cmd_screenshot = "xfce4-screenshooter -r -c -d 1"
+ cmd_alt_screenshot = "xfce4-screenshooter -w -c -d 0"
# Keyboard commands
cmd_media_play = "playerctl -i kdeconnect play-pause"
@@ -73,10 +44,9 @@ class Config(BaseConfig):
cmd_monitor_mode_day = "bash /home/kevin/bin/monitor_day.sh"
cmd_monitor_mode_night = "bash /home/kevin/bin/monitor_night.sh"
cmd_monitor_mode_alt = "bash /home/kevin/bin/monitor_gamenight.sh"
- cmd_reconfigure_screens = "kanshictl reload"
# Commands
- wallpaper_config_command = "/bin/true" # TODO: Remove
+ wallpaper_config_command = "/home/kevin/bin/wal-nitrogen-noupdate"
# Images
desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png"
@@ -115,8 +85,7 @@ class Config(BaseConfig):
# Bar variables
bar_background = background
- bar_rgba_opacity = "AA"
- bar_opacity = 1.0
+ bar_opacity = 0.8
bar_hover_opacity = 1
# Groupbox variables
@@ -137,12 +106,11 @@ class Config(BaseConfig):
tasklist_urgent_border = highlight
tasklist_font = "Noto Sans"
tasklist_fontsize = 11
- tasklist_rounded = False
# Thermal indicator variables
thermal_threshold = 75
- thermal_sensor = "Package id 0"
- thermal_chip = "coretemp-isa-0000"
+ thermal_sensor = "Tdie"
+ thermal_chip = "zenpower-pci-00c3"
# CPU graph variables
cpu_graph_colour = '#ff0000'
@@ -157,11 +125,10 @@ class Config(BaseConfig):
battery_theme_path = "/home/kevin/.config/qtile/kuro/resources/battery"
battery_update_delay = 5
- # Network variables
+ # Wifi variables
wifi_interface = "wifi0"
wifi_theme_path = "/home/kevin/.config/qtile/kuro/resources/wifi"
wifi_update_interval = 5
- wired_interface = "eth0"
# GPU variables
gpu_theme_path = "/home/kevin/.config/qtile/kuro/resources/gpu"
@@ -170,7 +137,9 @@ class Config(BaseConfig):
volume_font = "Noto Sans"
volume_fontsize = 11
volume_theme_path = "/home/kevin/.config/qtile/kuro/resources/volume"
- volume_pulse_sinks = []
+ volume_pulse_sink = "alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo-output"
+ volume_pulse_sink2 = "alsa_output.pci-0000_0d_00.4.analog-stereo"
+
volume_is_bluetooth_icon = False
volume_update_interval = 0.2
@@ -194,6 +163,9 @@ class Config(BaseConfig):
laptop_screen_nvidia = "eDP-1-1"
laptop_screen_intel = "eDP1"
+ # Keyboard colors
+ do_keyboard_updates = False
+
# Show audio visualizer
show_audio_visualizer = False
kill_unnecessary_glava_processes = True
@@ -207,15 +179,9 @@ class Config(BaseConfig):
# Show battery widget
show_battery_widget = False
+ # Audio control applications
+ # apps_audio = ["pavucontrol"]
+ apps_audio_afterstart = []
+
# Comma-separated list of ignored players in the media widget
media_ignore_players = "kdeconnect"
-
- @classmethod
- def initialize(cls, qtile):
- # Can do extra initialization based on qtile instance here
- super(Config, cls).initialize(qtile=qtile)
-
- # Replace some apps if launched in X11 mode
- if qtile.core.name == "x11":
- logger.warning("Launched in X11 mode, overriding some apps in Config to xorg-variants.")
- cls.app_launcher = "/home/kevin/bin/dmenu_wal.sh"
diff --git a/kuro/config/aria.py b/kuro/config/aria.py
deleted file mode 100644
index ebb87bf..0000000
--- a/kuro/config/aria.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from kuro.config import Config as GeneralConfig
-
-
-class Config(GeneralConfig):
- """
- Kuro QTile configuration overrides for Aria
- """
- config_name = "Aria"
-
- # Default Applications
- app_terminal = "terminator"
- app_launcher = "/home/kevin/bin/dmenu_wal.sh"
- cmd_brightness_up = "true"
- cmd_brightness_down = "true"
- cmd_screenshot = "xfce4-screenshooter -r -c -d 1"
- cmd_alt_screenshot = "xfce4-screenshooter -w -c -d 0"
- lock_command = "bash /home/kevin/bin/lock.sh"
- cliphistory_command = "true"
-
- # Autostart applications
- apps_autostart_group = [
- {'group': "", 'command': ["firefox"]},
- {'group': "", 'command': ["terminator"]},
- {'group': "", 'command': ["/usr/bin/rambox"]},
- {'group': "", 'command': ["thunar"]},
- {'group': "", 'command': ["thunderbird"]},
- {'group': "", 'command': ["spotify"]},
- ]
-
- # Thermal indicator variables
- thermal_sensor = "Package id 0"
- thermal_chip = "coretemp-isa-0000"
-
- # Network variables
- wifi_interface = "wifi0"
- wired_interface = "enp7s0"
-
- # Volume widget variables
- volume_pulse_sinks = [
- "alsa_output.pci-0000_00_1f.3.analog-stereo",
- ]
-
- # Screen organization
- laptop_screen_nvidia = "eDP-1-1"
- laptop_screen_intel = "eDP1"
diff --git a/kuro/config/meconopsis.py b/kuro/config/meconopsis.py
deleted file mode 100644
index e580c92..0000000
--- a/kuro/config/meconopsis.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from kuro.config import Config as GeneralConfig
-
-
-class Config(GeneralConfig):
- """
- Kuro QTile configuration overrides for Meconopsis
- """
- config_name = "Meconopsis"
-
- # Thermal indicator variables
- thermal_sensor = "Package id 0"
- thermal_chip = "coretemp-isa-0000"
-
- # Network variables
- wifi_interface = "wlp3s0"
- wired_interface = "enp4s0"
-
- # Volume widget variables
- volume_pulse_sinks = [
- # Analog jack
- "alsa_output.usb-CSCTEK_USB_Audio_and_HID_A34004801402-00.analog-stereo",
- ]
diff --git a/kuro/config/violet.py b/kuro/config/violet.py
deleted file mode 100644
index e6dd5a6..0000000
--- a/kuro/config/violet.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from kuro.config import Config as GeneralConfig
-
-
-class Config(GeneralConfig):
- """
- Kuro QTile configuration overrides for Violet
- """
- config_name = "Violet"
-
- # Thermal indicator variables
- thermal_sensor = "Tdie"
- thermal_chip = "zenpower-pci-00c3"
-
- # Network variables
- wifi_interface = None
- wired_interface = "br1"
-
- # Volume widget variables
- volume_pulse_sinks = [
- # Behringer USB mixer
- "alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo-output",
- # Motherboard output (Starship/Matisse)
- "alsa_output.pci-0000_0e_00.4.iec958-stereo",
- # PCIe card output (CMI8738/CMI8768 PCI Audio)
- "alsa_output.pci-0000_08_00.0.analog-stereo",
- ]
diff --git a/kuro/theme.py b/kuro/theme.py
index 7311976..b83e0d6 100644
--- a/kuro/theme.py
+++ b/kuro/theme.py
@@ -1,111 +1,130 @@
import json
import os
import random
-import time
import datetime
-import socket
-import subprocess
-from typing import Optional
-from libqtile.backend.base import Window
-from libqtile.backend.wayland.layer import LayerStatic
-from libqtile.backend.wayland.xwindow import XWindow as WaylandXWindow, XStatic as WaylandXStatic
-from libqtile.backend.x11.window import XWindow as XorgXWindow
# Initialize logging
from libqtile.log_utils import logger
-logger.warning("Importing qtile theme requirements...")
+logger.error("Importing qtile theme requirements...")
from libqtile.config import Key, Screen, Group, Drag, Click, Match
+from libqtile.command.base import expose_command
from libqtile.lazy import lazy
from libqtile import layout, bar, widget, qtile
-from qtile_extras import widget as extra_widget
-logger.warning("Importing theme util functions...")
+logger.error("Importing theme util functions...")
# Import theme util functions
from xcffib.xproto import WindowError
-logger.warning("Importing kuro utils...")
+logger.error("Importing kuro utils...")
import kuro.utils.widgets
from kuro.utils import general as utils
-logger.warning("Importing variables and other utils...")
+logger.error("Importing variables and other utils...")
# Import variables
from kuro.base import BaseTheme
from kuro.utils.general import display_wm_class, test_popups
+from kuro.utils.kb_backlight import handle_focus_change as kb_handle_focus_change
from kuro.utils import layouts as kuro_layouts
+from kuro.utils.windows import KuroStatic
-logger.warning("Importing configuration...")
+logger.error("Importing configuration...")
-from kuro.utils import load_config_class
-Config = load_config_class()
-if Config is None:
- raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
-Config.initialize(qtile)
-
-logger.warning("Imports done")
+try:
+ from kuro.config import Config
+except ImportError:
+ try:
+ from kuro.baseconfig import BaseConfig as Config
+ except ImportError:
+ Config = None
+ raise ImportError("Could not load theme Config or BaseConfig!")
+logger.error("Imports done")
class Kuro(BaseTheme):
# Shorthand for modifier key
mod = Config.get("modifier", "mod4")
+ # Show debug messages
+ debug = Config.get('debug', False)
+ debug_textfields = []
+ debug_bars = []
+
# Screen count
num_screens = 0
+ # Top bars
+ topbars = []
+
+ # Visualizers
+ audio_visualizers = []
+
# Static windows
static_windows = []
# Current wallpaper path
current_wallpaper = None
+ # Whether or not to perform keyboard backlight updates
+ do_keyboard_updates = True
+
# Window manager name
wmname = "qtile"
- startup_completed = False
- autostart_app_rules = {}
-
# Floating layout override
- floating_layout = kuro_layouts.KuroFloating(
- border_width=0,
- border_focus="#000000",
- border_normal="#000000",
- float_rules=[
- # Run the utility of `xprop` to see the wm class and name of an X client.
- *layout.Floating.default_float_rules,
- Match(wm_class='confirmreset'), # gitk
- Match(wm_class='makebranch'), # gitk
- Match(wm_class='maketag'), # gitk
- Match(wm_class='ssh-askpass'), # ssh-askpass
- Match(title='branchdialog'), # gitk
- Match(title='pinentry'), # GPG key password entry
- Match(title='origin.exe', wm_class='Wine'), # Wine Origin game launcher
+ floating_layout = kuro_layouts.KuroFloating(float_rules=[
+ # Run the utility of `xprop` to see the wm class and name of an X client.
+ *layout.Floating.default_float_rules,
+ Match(wm_class='confirmreset'), # gitk
+ Match(wm_class='makebranch'), # gitk
+ Match(wm_class='maketag'), # gitk
+ Match(wm_class='ssh-askpass'), # ssh-askpass
+ Match(title='branchdialog'), # gitk
+ Match(title='pinentry'), # GPG key password entry
+ Match(title='origin.exe', wm_class='Wine'), # Wine Origin game launcher
+ ])
- # Homebank popups
- Match(title='Add transaction', wm_class='homebank'),
- Match(title='Edit transaction', wm_class='homebank'),
- Match(title='Inherit transaction', wm_class='homebank'),
- Match(title='Multiple edit transactions', wm_class='homebank'),
- Match(title='Transaction splits', wm_class='homebank'),
- ]
- )
+ def set_debug_text(self, text):
+ for field in self.debug_textfields:
+ field.text = text
+ for bar in self.debug_bars:
+ if qtile is not None:
+ 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):
+ logger.error("Initializing Kuro theme...")
+ self.log_debug("Initializing Kuro Theme...")
+
# Update color scheme
- logger.warning("Initializing colorscheme...")
self.initialize_colorscheme()
- logger.warning("Initializing superclass...")
+ # Set settings
+ self.do_keyboard_updates = Config.get("do_keyboard_updates", True)
+
super(Kuro, self).initialize()
- logger.warning("Updating keys for groups and layouts...")
+ self.update()
+
+ def update(self):
+ # Update keys with keys for groups and layouts
self.update_keys()
def init_keys(self):
- logger.warning("Initializing keys")
+ self.log_debug("Initializing keys")
+
return [
# Switch between windows in current stack pane
Key([self.mod], "k", lazy.layout.down()),
@@ -127,6 +146,9 @@ class Kuro(BaseTheme):
# Floating toggle
Key([self.mod, "shift"], 'f', lazy.window.toggle_floating()),
+ # Pinned toggle
+ Key([self.mod, "shift"], 'p', lazy.function(self.toggle_pinned)),
+
# Toggle between split and unsplit sides of stack.
# Split = all windows displayed
# Unsplit = 1 window displayed, like Max layout, but still with
@@ -142,8 +164,8 @@ class Kuro(BaseTheme):
# Super-B to start webbrowser
Key([self.mod], "b", lazy.spawn(Config.get('web_browser', "xterm links"))),
- # Super-T to start file manager
- Key([self.mod], "t", lazy.spawn(Config.get('file_manager', "thunar"))),
+ # Super-F to start file manager
+# Key([self.mod], "f", lazy.spawn(Config.get('file_m4anager', "thunar"))),
# Super-Shift-R to start spawncmd
Key([self.mod, "shift"], "r", lazy.spawncmd()),
@@ -151,6 +173,10 @@ class Kuro(BaseTheme):
# Lock shortcut
Key([self.mod], "l", lazy.spawn(Config.get('lock_command', "i3lock"))),
+ # 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'))),
+
# Display modes
Key([self.mod], "Prior", lazy.spawn(Config.get('cmd_monitor_mode_3s144', 'true'))),
Key([self.mod], "Next", lazy.spawn(Config.get('cmd_monitor_mode_3s60', 'true'))),
@@ -158,10 +184,6 @@ class Kuro(BaseTheme):
Key([self.mod], "End", lazy.spawn(Config.get('cmd_monitor_mode_night', 'true'))),
Key([self.mod], "Insert", lazy.spawn(Config.get('cmd_monitor_mode_alt', 'true'))),
- # 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'))),
-
# Media keys
Key([], "XF86AudioPlay", lazy.spawn(Config.get('cmd_media_play', 'true'))),
Key([], "XF86AudioNext", lazy.spawn(Config.get('cmd_media_next', 'true'))),
@@ -178,31 +200,27 @@ class Kuro(BaseTheme):
# Alt screenshot
Key([self.mod], "Print", lazy.spawn(Config.get('cmd_alt_screenshot', 'xfce4-screenshooter'))),
- # Copy from clipboard history
- Key([self.mod, "control"], "c", lazy.spawn(Config.get('cliphistory_command', 'true'))),
-
# Toggle between different layouts as defined below
Key([self.mod], "Tab", lazy.next_layout()),
# Kill the current window
Key([self.mod], "w", lazy.window.kill()),
- # Restart/reload QTile (Restart only available in X11 backend)
- Key([self.mod, "control"], "r", lazy.restart() if qtile.core.name == "x11" else lazy.reload_config()),
+ # Restart QTile
+ Key([self.mod, "control"], "r", lazy.restart()),
# Shutdown QTile
Key([self.mod, "control"], "q", lazy.shutdown()),
# Update wallpaper
- Key([self.mod, "control"], "w",
- lazy.function(self.set_random_wallpaper), lazy.function(self.update_colorscheme)),
-
- # Reload screen configuration
- Key([self.mod, "control"], "s", lazy.spawn(Config.get('cmd_reconfigure_screens', 'true'))),
+ 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)),
+
# Toggle static windows
Key([self.mod], "p", lazy.function(self.toggle_window_static)),
@@ -210,7 +228,13 @@ class Kuro(BaseTheme):
##
# Debug keyboard shortcuts
##
- Key([self.mod, "shift", "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)),
+
+ # Update visualizer widgets
+ Key([self.mod, "shift", "control"], "v", lazy.function(self.reinitialize_visualizers)),
# Show extensive window info
Key([self.mod, "shift", "control"], "i", lazy.function(self.show_window_info)),
@@ -220,29 +244,34 @@ class Kuro(BaseTheme):
]
def init_groups(self):
- logger.warning("Initializing groups")
+ self.log_debug("Initializing groups")
+
+ groups = []
+
# http://fontawesome.io/cheatsheet
- groups = [
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group(""),
- Group("", layout='floating', layouts=[
- layout.Floating(
- border_focus="#990000",
- border_normal="#440000"
- )
- ])
- ]
+ groups.append(Group("", spawn=Config.get('web_browser', "true")))
+ groups.append(Group("", spawn=Config.get('app_terminal_init', "true")))
+ groups.append(Group(""))
+ groups.append(Group("", spawn=Config.get('app_chat', "true")))
+ groups.append(Group("", spawn=Config.get('app_irc', "true")))
+ groups.append(Group("", spawn=Config.get('file_manager', "true")))
+ groups.append(Group("", spawn=Config.get('app_mail', "true")))
+ groups.append(Group(""))
+ groups.append(Group("", spawn=Config.get('app_music', "true")))
+ groups.append(Group(""))
+ groups.append(Group("", spawn=Config.get('apps_audio', "true")))
+ groups.append(Group("", layout='floating', layouts=[
+ layout.Floating(
+ border_focus="#990000",
+ border_normal="#440000"
+ )
+ ]))
+
return groups
def init_layouts(self):
- logger.warning("Initializing layouts")
+ self.log_debug("Initializing layouts")
+
return [
kuro_layouts.KuroWmii(
theme=self,
@@ -262,7 +291,8 @@ class Kuro(BaseTheme):
]
def init_widget_defaults(self):
- logger.warning("Initializing widget_defaults")
+ self.log_debug("Initializing widget_defaults")
+
return {
"font": Config.get('font_topbar', "Sans"),
"fontsize": Config.get('fontsize_topbar', 16),
@@ -270,12 +300,158 @@ class Kuro(BaseTheme):
}
def init_screens(self):
- logger.warning("Initializing screens")
- self.reinit_screens()
- return self.screens
+ self.log_debug("Initializing screens")
+
+ self.num_screens = utils.get_screen_count()
+ if self.num_screens <= 0:
+ self.num_screens = 1
+
+ screens = []
+ for x in range(self.num_screens):
+ self.log_info("Initializing bars for screen {}".format(x))
+ widgets = []
+ widgets.extend([
+ widget.GroupBox(
+ active=Config.get('colour_groupbox_icon_active', '#ffffff'),
+ borderwidth=Config.get('width_groupbox_border', 1),
+ disable_drag=Config.get('bool_groupbox_disable_drag', False),
+ font=Config.get('font_groupbox', 'Arial'),
+ fontsize=Config.get('fontsize_groupbox', 15),
+ highlight_color=Config.get("colour_groupbox_border_normal", '#444444'),
+ inactive=Config.get('colour_groupbox_icon_inactive', '#444444'),
+ rounded=Config.get('bool_groupbox_rounded_borders', True),
+ this_current_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
+ this_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
+ margin=Config.get('margin_groupbox', 0)
+ ),
+ widget.Prompt(**self.widget_defaults),
+
+ kuro.utils.widgets.KuroTaskList(
+ 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)
+ )
+ ])
+
+ if Config.get('show_audio_visualizer', False):
+ widgets.append(kuro.utils.widgets.AudioVisualizerWidget(margin_x=0, margin_y=0))
+
+ widgets.extend([
+ kuro.utils.widgets.MediaWidget(ignore_players=Config.get('media_ignore_players', '')),
+ kuro.utils.widgets.TextSpacerWidget(fontsize=14),
+ ])
+
+ if Config.get('show_temperature', False):
+ widgets.extend([
+ kuro.utils.widgets.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),
+ update_interval=5,
+ fontsize_left=18, fontsize_right=11
+ ),
+ ])
+
+ widgets.extend([
+ kuro.utils.widgets.CPUInfoWidget(fontsize_left=16, fontsize_right=11),
+ kuro.utils.widgets.MemoryInfoWidget(fontsize_left=18, fontsize_right=11),
+ kuro.utils.widgets.DiskIOInfoWidget(fontsize_left=16, fontsize_right=11),
+ ])
+ if Config.get('show_battery_widget', False):
+ widgets.extend([
+ kuro.utils.widgets.BatteryInfoWidget(fontsize_left=16, fontsize_right=11),
+ ])
+ widgets.extend([
+ kuro.utils.widgets.VolumeInfoWidget(
+ pulse_sink=Config.get('volume_pulse_sink', None),
+ fontsize_left=18,
+ fontsize_right=11,
+ font_left=Config.get('font_groupbox', None),
+ ),
+ kuro.utils.widgets.VolumeInfoWidget(
+ pulse_sink=Config.get('volume_pulse_sink2', None),
+ fontsize_left=18,
+ fontsize_right=11,
+ font_left=Config.get('font_groupbox', None),
+ ),
+ kuro.utils.widgets.TextSpacerWidget(fontsize=14),
+ kuro.utils.widgets.NetworkInfoWidget(fontsize_left=16, fontsize_right=14),
+ ])
+ if Config.get('show_gpu_widget', False):
+ widgets.extend([
+ kuro.utils.widgets.GPUStatusWidget(
+ theme_path=Config.get('gpu_theme_path', '/home/docs/checkouts/readthedocs.org/user_builds/qtile'
+ '/checkouts/latest/libqtile/resources/battery-icons'),
+ padding=0,
+ )
+ ])
+ widgets.extend([
+ kuro.utils.widgets.TextSpacerWidget(fontsize=14),
+ ])
+
+ # Systray can only be on one screen, so put it on the first
+ if x == 0:
+ widgets.append(widget.Systray(**self.widget_defaults))
+
+ widgets.extend([
+ kuro.utils.widgets.KuroCurrentLayoutIcon(custom_icon_paths=Config.get('custom_layout_icon_paths', [])),
+ widget.Clock(format="%a %d %b, %H:%M", **self.widget_defaults),
+ kuro.utils.widgets.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}'),
+ execute=Config.get('updates_execute_command', None),
+ update_interval=Config.get('updates_interval', 600),
+ **self.widget_defaults
+ ),
+ widget.TextBox("#{}".format(x), name="default", **self.widget_defaults),
+ ])
+
+ topbar = utils.KuroTopBar(
+ theme=self,
+ background=Config.get('bar_background', '#000000'),
+ opacity=Config.get('bar_opacity', 1.0),
+ widgets=widgets,
+ 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
+ if Config.get('debug', False):
+ 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.extend([
+ widget.TextBox(" Debugging bar ", name="default", **self.widget_defaults),
+ textfield,
+ ])
+ screens[x].bottom = bar.Bar(
+ widgets=widgets,
+ size=Config.get('height_debugbar', 30)
+ )
+ self.debug_bars.append(screens[x].bottom)
+
+ return screens
def init_mouse(self):
- logger.warning("Initializing mouse")
+ self.log_debug("Initializing mouse")
+
# Drag floating layouts.
mouse = [
Drag([self.mod], "Button1", lazy.window.set_position_floating(),
@@ -284,50 +460,12 @@ class Kuro(BaseTheme):
start=lazy.window.get_size()),
Click([self.mod], "Button2", lazy.window.bring_to_front())
]
+
return mouse
- def initialize_colorscheme(self):
- 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']
- Config.bar_background = colors['color1']
-
- def reinit_screens(self):
- # TODO: Move backend check into utils method
- if qtile.core.name == "x11":
- self.num_screens = max(1, utils.get_screen_count())
- else:
- self.num_screens = max(1, len(qtile.core.get_screen_info()))
- logger.warning(f"Detected {self.num_screens} screens.")
-
- for x in range(self.num_screens):
- logger.warning("Reconfiguring bars for screen {}".format(x))
-
- try:
- screen = self.screens[x]
- except IndexError:
- screen = Screen()
-
- if screen.top is None:
- screen.top = self.build_bar_for_screen(x)
- topbar = screen.top
-
- self.screens.append(Screen(top=topbar))
-
def update_keys(self):
- logger.warning("Updating keys")
+ self.log_debug("Updating keys")
+
for i, g in enumerate(self.groups):
if i == 9:
i = -1
@@ -359,229 +497,124 @@ class Kuro(BaseTheme):
Key([self.mod], "n", lazy.layout.normalize()),
])
- def build_bar_for_screen(self, screen_num):
- widgets = [
- # Workspaces
- kuro.utils.widgets.KuroGroupBox(
- active=Config.get('colour_groupbox_icon_active', '#ffffff'),
- borderwidth=Config.get('width_groupbox_border', 1),
- disable_drag=Config.get('bool_groupbox_disable_drag', False),
- font=Config.get('font_groupbox', 'Arial'),
- fontsize=Config.get('fontsize_groupbox', 15),
- highlight_color=Config.get("colour_groupbox_border_normal", '#444444'),
- inactive=Config.get('colour_groupbox_icon_inactive', '#444444'),
- rounded=Config.get('bool_groupbox_rounded_borders', True),
- this_current_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
- this_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
- margin=Config.get('margin_groupbox', 0)
- ),
+ # Util functions
+ @staticmethod
+ def redraw_bar(qtile):
+ for s in qtile.screens:
+ s.top.draw()
- # Spawn prompt (only shown if activated)
- widget.Prompt(**self.widget_defaults),
+ @staticmethod
+ def update_screens(qtile):
+ out = utils.call_process(["xrandr", "--current"])
+ video_mode = "nvidia"
+ #if "nvidia" in mode_out:
+ # video_mode = "nvidia"
+ #elif "intel" in mode_out:
+ # video_mode = "intel"
+ laptop_screen = None
+ screens = []
+ for x in out.split("\n"):
+ if " connected " in x:
+ if Config.get("laptop_screen_nvidia", None) is not None \
+ and Config.get("laptop_screen_intel", None) is not None:
+ if video_mode == "nvidia" and Config.get("laptop_screen_nvidia", None) in x:
+ laptop_screen = x
+ elif video_mode == "intel" and Config.get("laptop_screen_intel", None) in x:
+ laptop_screen = x
+ else:
+ screens.append(x)
+ else:
+ screens.append(x)
- # Open window list
- 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),
- icon_size=Config.get('tasklist_iconsize', 17),
- theme_mode=Config.get('tasklist_thememode', 'preferred'),
- theme_path=Config.get('tasklist_themepath', '/usr/share/icons/Papirus-Dark'),
- txt_floating=" ", txt_maximized=" ", txt_minimized=" ",
- )
- ]
+ # 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()
+ else:
+ utils.execute("arandr")
- # Media widget(s)
- widgets.extend([
- # An MPRIS widget that shows the media play status as an icon.
- widget.Mpris2(
- font=Config.get('font_groupbox', 'Arial'),
- fontsize=Config.get('fontsize_groupbox', 15),
- format="",
- scroll=False,
- playing_text="",
- paused_text="",
- stopped_text="",
- no_metadata_text="",
- name=f"media_icon{screen_num}",
- mouse_callbacks={
- "Button1": lazy.widget[f"media_icon{screen_num}"].play_pause(),
- "Button3": lazy.widget[f"media_icon{screen_num}"].next(),
- "Button4": lambda: None,
- "Button5": lambda: None,
- }
- ),
- # An MPRIS widget that shows the currently playing song information in a nice format.
- widget.Mpris2(
- font=Config.get('font_topbar', 'Arial'),
- fontsize=Config.get('fontsize_topbar', 15),
- format="{xesam:title} - {xesam:artist} - {xesam:album}",
- scroll=True,
- width=300, # Maximum width before widget starts scrolling
- playing_text="{track}",
- paused_text="{track}",
- stopped_text="",
- no_metadata_text="No metadata available",
- name=f"media_text{screen_num}",
- mouse_callbacks={
- "Button1": lazy.widget[f"media_icon{screen_num}"].play_pause(),
- "Button3": lazy.widget[f"media_icon{screen_num}"].next(),
- "Button4": lambda: None,
- "Button5": lambda: None,
- }
- ),
- # An MPRIS widget masquerading as a text widget, that only shows "|" when media is playing or paused.
- widget.Mpris2(
- fontsize=14,
- format="",
- scroll=False,
- playing_text="|",
- paused_text="|",
- stopped_text="",
- no_metadata_text="",
- mouse_callbacks={
- "Button1": lambda: None,
- "Button4": lambda: None,
- "Button5": lambda: None,
- }
- )
- ])
+ def reinitialize_visualizers(self):
+ if Config.get("show_audio_visualizer", False):
+ logger.warning("Reinitializing visualizers...")
+ for screen in qtile.screens:
+ for widget in screen.top.widgets:
+ if isinstance(widget, kuro.utils.widgets.AudioVisualizerWidget):
+ if widget.client is not None:
+ widget.client.kill()
+ widget.client = None
+ widget.screen = None
+ self.update_visualizers()
- # Sensor widgets
- sensor_widgets = []
+ def update_visualizers(self):
+ if Config.get("show_audio_visualizer", False):
+ logger.warning("Updating visualizers..")
+ for screen in qtile.screens:
+ for widget in screen.top.widgets:
+ if isinstance(widget, kuro.utils.widgets.AudioVisualizerWidget):
+ if widget.client is None:
+ logger.warning("Spawning for screen {}".format(screen))
+ utils.execute(Config.get('visualizer_app', "glava"))
+ else:
+ widget.update_graph()
- # Temperature sensor
- if Config.get('show_temperature', False):
- sensor_widgets.append(kuro.utils.widgets.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),
- update_interval=5,
- fontsize_left=18, fontsize_right=11
- ))
+ def show_window_info(self, qtile):
+ window = qtile.current_window if qtile else None
- # CPU/Memory/Disk sensors
- sensor_widgets.extend([
- kuro.utils.widgets.CPUInfoWidget(fontsize_left=16, fontsize_right=11),
- kuro.utils.widgets.MemoryInfoWidget(fontsize_left=18, fontsize_right=11),
- kuro.utils.widgets.DiskIOInfoWidget(fontsize_left=16, fontsize_right=11),
- ])
+ import pprint
+ if window:
+ info = window.cmd_inspect() or None
+ name = window.name
- widgets.extend([
- widget.WidgetBox(
- font=Config.get('font_groupbox', 'Arial'),
- fontsize=Config.get('fontsize_groupbox', 15),
- text_open="[>]",
- text_closed="[]",
- widgets=sensor_widgets
- ),
- widget.TextBox(fontsize=14, text="|")
- ])
+ utils.notify(title="Window properties {}".format(name),
+ content="{}".format(pprint.pformat(vars(window))))
+ logger.warning("{}".format(pprint.pformat(vars(window))))
- # Battery level
- if Config.get('show_battery_widget', False):
- widgets.extend([
- kuro.utils.widgets.BatteryInfoWidget(fontsize_left=16, fontsize_right=11),
- ])
+ if info:
+ info = pprint.pformat(info)
+ utils.notify(title="Window info of {}".format(name),
+ content="{}".format(info))
+ logger.warning("{}".format(info))
- # Volume widget(s)
- for sink_name in Config.get('volume_pulse_sinks', []):
- widgets.append(
- kuro.utils.widgets.VolumeInfoWidget(
- pulse_sink=sink_name,
- fontsize_left=18,
- fontsize_right=11,
- font_left=Config.get('font_groupbox', None),
- )
- )
+ # @staticmethod
+ def toggle_window_static(self, qtile):
+ window = qtile.current_window
+ if window in self.static_windows:
+ utils.notify("Unpinned {}".format(window.name), "{} has been unpinned".format(window.name))
+ self.static_windows.remove(window)
+ del window.is_static_window
+ else:
+ utils.notify("Pinned {}".format(window.name), "{} has been pinned".format(window.name))
+ self.static_windows.append(window)
+ window.is_static_window = True
- # Network information
- widgets.extend([
- widget.TextBox(fontsize=14, text="|"),
- kuro.utils.widgets.NetworkInfoWidget(
- fontsize_left=16, fontsize_right=14,
- wireless_interface=Config.get('wifi_interface', None),
- wired_interface=Config.get('wired_interface', None)
- ),
- ])
+ window.floating = True
- # GPU widget
- if Config.get('show_gpu_widget', False):
- widgets.extend([
- kuro.utils.widgets.GPUStatusWidget(
- theme_path=Config.get(
- 'gpu_theme_path',
- '/usr/lib/python3.11/site-packages/libqtile/resources/battery-icons'),
- padding=0,
- )
- ])
- widgets.append(widget.TextBox(fontsize=14, text="|"))
-
- # Determine systray to use (X or Wayland)
- # X system tray can only be on one screen, so only put it on the first
- if qtile.core.name == "x11" and screen_num == 0:
- widgets.append(widget.Systray(**self.widget_defaults))
- elif qtile.core.name != "x11":
- widgets.append(extra_widget.StatusNotifier(**self.widget_defaults))
-
- # Layout switcher, clock and Screen ID
- widgets.extend([
- widget.CurrentLayoutIcon(custom_icon_paths=Config.get('custom_layout_icon_paths', [])),
- widget.Clock(format="%a %d %b ", **self.widget_defaults),
- widget.Clock(
- format="%H:%M",
- font=Config.get('font_topbar', "Sans"),
- fontsize=16,
- padding=0,
- ),
- widget.Clock(
- format=":%S ",
- font=Config.get('font_topbar', "Sans"),
- fontsize=11,
- padding=0,
- ),
- widget.CurrentScreen(
- active_color="66FF66", inactive_color="FFFFFF",
- active_text=f"#{screen_num}", inactive_text=f"#{screen_num}",
- **self.widget_defaults
- )
- ])
-
- # Build the bar
- return bar.Bar(
- background=f"{Config.get('bar_background', '#000000')}{Config.get('bar_rgba_opacity', 'AA')}",
- opacity=Config.get('bar_opacity', 1.0),
- widgets=widgets,
- size=Config.get('height_groupbox', 30)
- )
+ # Pinned toggle function
+ @staticmethod
+ def toggle_pinned(qtile):
+ windows = qtile.cmd_windows()
+ print(windows)
# QTile base callbacks
def callback_startup_once(self, *args, **kwargs):
- logger.warning("Callback Startup Once")
if not hasattr(qtile, 'theme_instance'):
# Save theme instance in qtile
qtile.theme_instance = self
- self.set_random_wallpaper()
+ self.update_wallpaper(qtile)
+
+ # Setup audio
+ # p = utils.execute_once(["qjackctl"])
+ # p.wait()
def callback_startup(self):
- logger.warning("Callback Startup")
if not hasattr(qtile, 'theme_instance'):
# Save theme instance in qtile
qtile.theme_instance = self
- logger.warning("Restoring wallpaper...")
if self.current_wallpaper:
+ #p = utils.execute_once(["wal", "-n", "-i", "{}".format(self.current_wallpaper)])
p = utils.execute_once(["wallust", "run", "{}".format(self.current_wallpaper)])
p.wait()
else:
@@ -593,91 +626,38 @@ class Kuro(BaseTheme):
except KeyError:
wallpaper = None
if wallpaper:
- self.set_wallpaper(wallpaper)
+ Kuro.set_wallpaper(qtile, wallpaper)
else:
- logger.warning("No wallpaper to restore.")
+ p = utils.execute_once("nitrogen --restore")
+ p.wait()
+
+ self.log_info("Starting compositor...")
+ utils.execute_once("picom -b")
+
+ self.log_info("Starting clipboard manager...")
+ utils.execute_once("xfce4-clipman")
+
+ self.log_info("Starting notification daemon...")
+ utils.execute_once("dunst")
+
+ self.log_info("Starting xiccd color profile manager...")
+ utils.execute_once("xiccd")
+
+ #self.log_info("Starting KDE connect daemon...")
+ #utils.execute_once("/usr/lib/kdeconnectd")
+
+ self.log_info("Starting KDE connect indicator...")
+ utils.execute_once("/usr/bin/kdeconnect-indicator")
+
+ self.log_info("Starting automatic backup scheduler...")
+ utils.execute_once("/usr/bin/vorta")
# Update color scheme
- self.update_colorscheme()
+ self.initialize_colorscheme()
- def callback_startup_complete(self, *args, **kwargs):
- logger.warning("Callback Startup Complete")
- if not hasattr(qtile, 'theme_instance'):
- # Save theme instance in qtile
- qtile.theme_instance = self
-
- # Update color scheme
- self.update_colorscheme()
-
- # Setup XDG Desktop Portal on Wayland
- if qtile.core.name == "wayland":
- self.setup_xdg_desktop_portal()
-
- # After first startup is complete, autostart configured apps
- logger.warning("Autostarting apps...")
- for category in Config.get("apps_autostart", {}).keys():
- if qtile.core.name == category or category == "common":
- logger.warning(f"Autostarting apps for {category}...")
- for app in Config.get("apps_autostart", {}).get(category, []):
- logger.warning(f"Starting '{app}'...")
- utils.execute_once(app)
- else:
- logger.warning(f"Skipping autostart apps for {category}, because core is {qtile.core.name}...")
-
- for app in Config.get("apps_autostart_group", []):
- if all(x in app.keys() for x in ["group", "command"]):
- logger.warning(f"Starting '{app['command']}' in group {app['group']}...")
- utils.start_in_group_once(theme=self, qtile=qtile, **app)
- else:
- logger.warning(f"Invalid app in 'apps_autostart_group', "
- f"must have 'group' and 'command' keys: {app}...")
- logger.warning("Autostart complete")
- cur_time = time.time()
- logger.warning(f"QTile startup completed! Started up in {(cur_time - self.startup_time):.1f} seconds!")
- self.startup_completed = True
-
- def callback_client_managed(self, *args, **kwargs):
- client: Optional[Window] = args[0] if len(args) > 0 else None
-
- # TODO: Move get_pid to an utility function
- w_pid = None
- try:
- w_pid = client.get_pid()
- except AttributeError: # Some windows might not have this .get_pid method. Try other ways
- if isinstance(client, WaylandXWindow) or isinstance(client, WaylandXStatic):
- w_pid = client.surface.pid
- elif isinstance(client, XorgXWindow):
- w_pid = client.get_net_wm_pid()
- elif isinstance(client, LayerStatic):
- pass # Wayland background layer 'window'
- else:
- logger.error(f"Unknown window type {client.__class__.__name__}")
-
- if w_pid is not None and w_pid in self.autostart_app_rules.keys():
- rule_id = self.autostart_app_rules[w_pid]
- logger.warning(f"Removing rule {rule_id} for PID {w_pid}, client {client.name}")
- lazy.remove_rule(rule_id)
-
- def callback_client_killed(self, *args, **kwargs):
- client = args[0]
- logger.warning("Client {} Killed".format(client))
-
- # If this window was static, remove it from the static window list
- if hasattr(client, "is_static_window") and client.is_static_window:
- logger.warning("Removing static window {}".format(client.name))
- del client.is_static_window
- self.static_windows.remove(client)
-
- def callback_screen_change(self, *args, **kwargs):
- logger.warning(f"Screen configuration changed, reinitializing screens")
- self.reinit_screens()
- qtile.reconfigure_screens()
- #qtile.reconfigure_screens() # Twice, see: https://github.com/qtile/qtile/issues/4673#issuecomment-2196459114
-
- #def callback_screens_reconfigured(self, *args, **kwargs):
- logger.warning(f"Screens were reconfgured, updating wallpapers and color scheme")
- self.set_wallpaper(self.current_wallpaper)
- self.update_colorscheme()
+ # def callback_screen_change(self, *args, **kwargs):
+ # for window in self.static_windows:
+ # window.togroup()
def callback_setgroup(self, *args, **kwargs):
for window in self.static_windows:
@@ -690,28 +670,121 @@ class Kuro(BaseTheme):
del window.is_static_window
self.static_windows.remove(window)
- def show_window_info(self, *args, **kwargs):
- import pprint
- window = qtile.current_window
- if window:
- logger.warning(f"Window properties {window.name}\n{pprint.pformat(vars(window))}")
- if window.info():
- logger.warning(f"Window info of {window.name}\n{pprint.pformat(window.info())}")
+ def callback_focus_change(self, *args, **kwargs):
+ if self.do_keyboard_updates:
+ kb_handle_focus_change(self)
- def toggle_window_static(self, *args, **kwargs):
- window = qtile.current_window
- if window in self.static_windows:
- utils.notify(qtile, "Unpinned {}".format(window.name), "{} has been unpinned".format(window.name))
- self.static_windows.remove(window)
- del window.is_static_window
- else:
- utils.notify(qtile, "Pinned {}".format(window.name), "{} has been pinned".format(window.name))
- self.static_windows.append(window)
- window.is_static_window = True
+ initial_windows = []
- window.floating = True
+ def callback_startup_complete(self, *args, **kwargs):
+ if not hasattr(qtile, 'theme_instance'):
+ # Save theme instance in qtile
+ qtile.theme_instance = self
+
+ # Only run on first startup
+ if not qtile.no_spawn:
+ dg = qtile.dgroups
+ for r in dg.rules:
+ pid = -1
+ # noinspection PyProtectedMember
+ for m in r.matchlist:
+ if m._rules.get('net_wm_pid', None) is not None:
+ pid = m._rules.get('net_wm_pid')
+ break
+ if pid != -1:
+ self.initial_windows.append((pid, r.group))
- def set_random_wallpaper(self, *args, **kwargs):
+ self.callback_client_new()
+
+ # After first startup is complete, start the audio apps that can only be started after boot is complete
+ if not qtile.no_spawn:
+ for app in Config.get("apps_audio_afterstart", []):
+ utils.execute_once(app)
+
+ # Update color scheme
+ Kuro.update_colorscheme(qtile)
+
+ def callback_client_new(self, *args, **kwargs):
+ client = args[0] if len(args) > 0 else None
+
+ if len(self.initial_windows) > 0:
+ init = self.initial_windows.copy()
+ for pid, gname in init:
+ for group in qtile.groups:
+ if len(group.windows) > 0:
+ for window in group.windows:
+ w_pid = window.window.get_net_wm_pid()
+ self.log_info("Comparing pid {} with window PID {}, window {}".format(pid, w_pid,
+ window.name))
+ if pid == w_pid:
+ c = qtile.dgroups.rules_map.copy()
+ for rid, r in c.items():
+ if r.matches(window):
+ 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(str(qtile.dgroups.rules_map))
+
+ # Check if it is a visualizer
+ if Config.get("show_audio_visualizer", False):
+ if client is not None and client.window.get_name() == "GLava":
+ placed = False
+ for screen in qtile.screens:
+ for widget in screen.top.widgets:
+ if not placed and isinstance(widget, kuro.utils.widgets.AudioVisualizerWidget):
+ if widget.client is None:
+ viz_info = widget.info()
+ pos_x = viz_info['offset'] + widget.margin_x
+ pos_y = 0 + widget.margin_y
+ width = viz_info['width'] - (2 * widget.margin_x)
+ height = viz_info['height'] - (2 * widget.margin_y)
+ screen_index = qtile.screens.index(screen)
+ logger.warning("Attaching {} {} to {} on screen {}".format(client, client.window.wid, type(widget).__name__, screen_index))
+ c = KuroStatic.create(client, screen, x=pos_x, y=pos_y, width=width, height=height)
+ c.opacity = Config.get("bar_opacity", 1.0)
+ widget.set_client(c, screen)
+ placed = True
+ if not placed:
+ if Config.get("kill_unnecessary_glava_processes", False):
+ logger.warning("Killing GLava {} because there is no widget where it can fit".format(client))
+ utils.notify("Glava", "Killing new GLava process because there is no screen without a visualizer")
+ client.kill()
+ else:
+ logger.warning("Not repositioning GLava {} because there is no widget where it can fit".format(client))
+ utils.notify("Glava", "Not repisitioning new GLava process because there is no screen without a visualizer")
+
+ # If this is Non-Mixer, move it to the audio group
+ logger.warning("Processing window {}".format(client))
+ if client is not None and client.window.get_wm_class() == ('Non-Mixer', 'Non-Mixer'):
+ logger.warning("Moving to correct group!")
+ client.window.togroup("")
+
+
+
+ def callback_client_killed(self, *args, **kwargs):
+ client = args[0]
+ logger.warning("Client {} Killed".format(client))
+
+ # Detach visualizer from widget if it was a visualizer window
+ if isinstance(client, KuroStatic):
+ for screen in qtile.screens:
+ for widget in screen.top.widgets:
+ if isinstance(widget, kuro.utils.widgets.AudioVisualizerWidget):
+ if widget.client == client:
+ screen_index = qtile.screens.index(screen)
+ logger.warning("Detaching {} {} from widget {} on screen {}".format(client, client.window.wid, type(widget).__name__, screen_index))
+ widget.client = None
+ widget.screen = None
+
+ # If this window was static, remove it from the static window list
+ if hasattr(client, "is_static_window") and client.is_static_window:
+ logger.warning("Removing static window {}".format(client.name))
+ del client.is_static_window
+ self.static_windows.remove(client)
+
+ @staticmethod
+ def update_wallpaper(qtile):
wallpapers = []
wallpaper_dir = Config.get("desktop_bg_folder", "")
@@ -727,39 +800,59 @@ class Kuro(BaseTheme):
except os.error as e:
logger.warning("Could not load wallpapers from directory: {}".format(e))
+
if wallpapers:
if Config.get("desktop_bg_override", False):
- wallpaper_file = Config.get("desktop_bg_override", "")
+ qtile.theme_instance.current_wallpaper = Config.get("desktop_bg_override", "")
else:
- wallpaper_file = os.path.join(wallpaper_dir, random.choice(wallpapers))
- logger.warning(f"Selected new wallpaper: {wallpaper_file}")
- self.set_wallpaper(wallpaper_file)
+ qtile.theme_instance.current_wallpaper = os.path.join(wallpaper_dir, random.choice(wallpapers))
+ logger.warning("Selected new wallpaper: {}".format(qtile.theme_instance.current_wallpaper))
+ Kuro.set_wallpaper(qtile, qtile.theme_instance.current_wallpaper)
else:
- logger.warning("Random wallpaper requested but no wallpapers are available.")
+ utils.execute_once("nitrogen --restore")
- def set_wallpaper(self, filename):
- if qtile.core.name == "x11":
- p = utils.execute_once(f"{Config.get('wallpaper_config_command', 'wal-nitrogen-noupdate')} {filename}")
- p.wait()
- else:
- # Wayland can set wallpaper in qtile directly per screen
- for screen_i, screen in enumerate(qtile.screens):
- sinfo = screen.info()
- sfilename = filename
- if sinfo.get('width', 100) < sinfo.get('height', 10):
- # Vertical screen, see if there is a vertical alt wallpaper
- basename, ext = os.path.splitext(filename)
- new_filename = f"{basename}.vertical{ext}"
- if os.path.isfile(new_filename):
- sfilename = new_filename
- logger.warning(f"Setting Screen#{screen_i} wallpaper to {sfilename}.")
- screen.set_wallpaper(sfilename, "fill")
- self.current_wallpaper = filename
- def update_colorscheme(self, *args, **kwargs):
- if self.current_wallpaper:
- logger.warning(f"Updating wal colors for wallpaper {self.current_wallpaper}")
- p = utils.execute_once(["wallust", "run", "{}".format(self.current_wallpaper)])
+ @staticmethod
+ def set_wallpaper(qtile, filename):
+ p = utils.execute_once("{} {}".format(Config.get('wallpaper_config_command', 'wal-nitrogen-noupdate'),
+ filename))
+ p.wait()
+ 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']
+ Config.bar_background = colors['color1']
+
+ @staticmethod
+ def update_colorscheme(qtile):
+ """
+ :type qtile: libqtile.manager.Qtile
+ """
+ if qtile.theme_instance.current_wallpaper:
+ #p = utils.execute(["wal", "-n", "-i", "{}".format(qtile.theme_instance.current_wallpaper)])
+ p = utils.execute(["wallust", "run", "{}".format(qtile.theme_instance.current_wallpaper)])
p.wait()
colors = None
@@ -788,81 +881,62 @@ class Kuro(BaseTheme):
layout.border_normal = colors['color1']
layout.border_normal_stack = colors['color1']
- for screen_i, screen in enumerate(qtile.screens):
+ for screen in qtile.screens:
bar = screen.top
- logger.warning(f"Updating colorscheme for screen {screen_i}")
- if bar:
- bar.background = f"{colors['color1']}{Config.get('bar_rgba_opacity', 'AA')}"
- bar.drawer.clear(bar.background)
+ 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 for widget {}: {}".format(w, e))
- def update_widget(w):
- if hasattr(w, '_update_drawer'):
- try:
- w._update_drawer()
- except Exception as e:
- logger.error("Error while updating drawer for widget {}: {}".format(w, e))
+ if hasattr(w, 'foreground'):
+ w.foreground = colors['color15']
- if hasattr(w, 'foreground'):
- w.foreground = colors['color15']
+ if hasattr(w, 'foreground_normal'):
+ w.foreground_normal = 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, 'foreground_alert'):
- w.foreground_alert = colors['color3']
+ if hasattr(w, 'border'):
+ w.border = colors['color15']
- if hasattr(w, 'border'):
- w.border = colors['color15']
+ if hasattr(w, 'active'):
+ w.active = colors['color15']
- if hasattr(w, 'active'):
- w.active = colors['color15']
+ if hasattr(w, 'highlight_color'):
+ w.highlight_color = colors['color3']
- if hasattr(w, 'highlight_color'):
- w.highlight_color = colors['color3']
+ if hasattr(w, 'inactive'):
+ w.inactive = colors['color8']
- 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_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, '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_current_screen_border'):
- w.other_current_screen_border = colors['color8']
+ if hasattr(w, 'other_screen_border'):
+ w.other_screen_border = colors['color8']
- if hasattr(w, 'other_screen_border'):
- w.other_screen_border = colors['color8']
+ if isinstance(w, kuro.utils.widgets.AudioVisualizerWidget):
+ w.graph_color = colors['color15']
+ w.fill_color = colors['color8']
- if isinstance(w, widget.WidgetBox):
- for subw in w.widgets:
- update_widget(subw)
+ bar.draw()
- for w in bar.widgets:
- update_widget(w)
+ # Update colors in visualizers and restart visualizers
+ with open(Config.get("glava_color_file_path", "~/.config/glava/kurobars_color.glsl"), 'w') as f:
+ f.write("#define COLOR {}\n#request setbg {}00".format(colors['color15'], colors['color1'][1:]))
+ qtile.theme_instance.reinitialize_visualizers()
- bar.draw()
- else:
- logger.warning(f"Screen {screen_i} has no bar?")
-
- # Attempt to call pywalfox to update firefox/thunderbird colors
- try:
- logger.warning(f"Calling 'pywalfox update'...")
- p = utils.execute(["pywalfox", "update"])
- p.wait()
- except subprocess.SubprocessError as e:
- logger.error(f"Error running 'pywalfox update': {e}")
-
- utils.notify(qtile,
- "Updated colorscheme!",
- f"active: {colors['color15']}, inactive: {colors['color1']}")
-
- def setup_xdg_desktop_portal(self):
- # XDG Desktop portal is used for screensharing, screenshots and filepickers in wayland.
- # To work correctly, it needs to have two env variables set in the systemd user session
- logger.warning(f"Setting XDG_CURRENT_DESKTOP env and updating XDG Desktop Portal configuration...")
- os.environ["XDG_CURRENT_DESKTOP"] = "qtile"
- subprocess.Popen(["systemctl", "--user", "import-environment", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP"])
- subprocess.Popen(["dbus-update-activation-environment", "--systemd", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP=qtile"])
- subprocess.Popen(["systemctl", "--user", "restart", "xdg-desktop-portal"])
+ utils.notify(
+ "Updated colorscheme!",
+ "active: {}, inactive: {}".format(colors['color15'], colors['color1'])
+ )
diff --git a/kuro/utils/__init__.py b/kuro/utils/__init__.py
index 7d8a838..e69de29 100644
--- a/kuro/utils/__init__.py
+++ b/kuro/utils/__init__.py
@@ -1,31 +0,0 @@
-import importlib
-import socket
-import traceback
-from libqtile.log_utils import logger
-
-def load_config_class():
- # Try to import host-specific configuration first
- hostname = socket.gethostname().lower()
- if hostname:
- try:
- host_module = importlib.import_module(f"kuro.config.{hostname}")
- return getattr(host_module, "Config")
- except ImportError:
- pass
- logger.warning(f"No host-specific configuration available for {hostname}. Loading general config...")
-
- # If no config yet, load general Kuro Config object
- try:
- conf_module = importlib.import_module("kuro.config")
- return getattr(conf_module, "Config")
- except ImportError as e:
- logger.error(traceback.format_exc())
- logger.error("Could not load Kuro Config. Trying to load BaseConfig. Error: {}".format(e))
-
- # If no config yet, load fallback BaseConfig
- try:
- base_module = importlib.import_module("kuro.base")
- return getattr(base_module, "BaseConfig")
- except ImportError as e:
- logger.error(traceback.format_exc())
- return None
diff --git a/kuro/utils/general.py b/kuro/utils/general.py
index c8d6a8c..f63e1ad 100644
--- a/kuro/utils/general.py
+++ b/kuro/utils/general.py
@@ -2,14 +2,20 @@ import re
import subprocess
import traceback
from time import sleep
-from typing import List
import notify2
+import six
from dbus import DBusException
from libqtile import widget
+from libqtile.backend.x11.window import Internal
+from libqtile.bar import Bar
from notify2 import Notification, URGENCY_NORMAL
from libqtile.log_utils import logger
-from libqtile import qtile
+
+try:
+ notify2.init("QTileWM")
+except DBusException as e:
+ logger.error("Could not initialize notify2: {}".format(e))
BUTTON_LEFT = 1
BUTTON_MIDDLE = 2
@@ -32,68 +38,40 @@ def is_running(process):
def execute(process):
- try:
+ 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):
if isinstance(process, list):
return subprocess.Popen(process)
elif isinstance(process, str):
return subprocess.Popen(process.split())
else:
- logger.info(f"Failed to execute_once")
- except FileNotFoundError as e:
- logger.error(f"Could not execute {process}, FileNotFoundError - {e}")
-
-
-def execute_once(process):
- logger.info(f"Attempting to execute_once: {process}")
- if not is_running(process):
- return execute(process)
- logger.info(f"Process was already running: {process}")
-
-
-def start_in_group(theme, qtile, group: str, command: List[str], floating: bool = False,
- intrusive: bool = False, dont_break: bool = False):
- try:
- proc = subprocess.Popen(command)
- match_args = {"net_wm_pid": proc.pid}
- rule_args = {
- "float": floating,
- "intrusive": intrusive,
- "group": group,
- "break_on_match": not dont_break,
- }
- rule_id = qtile.add_rule(match_args, rule_args)
- theme.autostart_app_rules[proc.pid] = rule_id
- return proc
- except FileNotFoundError as e:
- logger.error(f"Could not execute {process}, FileNotFoundError - {e}")
-
-
-def start_in_group_once(theme, qtile, group: str, command: List[str], floating: bool = False,
- intrusive: bool = False, dont_break: bool = False):
- logger.info(f"Attempting to start_in_group_once: {command}")
- if not is_running(command):
- return start_in_group(theme=theme, qtile=qtile, group=group, command=command,
- floating=floating, intrusive=intrusive, dont_break=dont_break)
- logger.info(f"Process was already running: {command}")
+ pass
def call_process(command, **kwargs):
"""
- Run the given command and return the string from stdout.
+ This method uses `subprocess.check_output` to run the given command
+ and return the string from stdout, which is decoded when using
+ Python 3.
"""
- return subprocess.check_output(command, **kwargs).decode()
+ output = subprocess.check_output(command, **kwargs)
+ if six.PY3:
+ output = output.decode()
+ return output
def get_screen_count():
try:
- if qtile.core.name == "x11":
- logger.info("Using xrandr to detect screen count")
- output = subprocess.check_output("xrandr -q".split()).decode('utf-8')
- output = [x for x in output.split("\n") if " connected" in x]
- else:
- logger.info("Using lsmon (wallutils) to detect screen count")
- output = subprocess.check_output(["lsmon"]).decode('utf-8')
- output = output.split("\n")
+ output = subprocess.check_output("xrandr -q".split()).decode('utf-8')
+ output = [x for x in output.split("\n") if " connected" in x]
except subprocess.CalledProcessError:
return 1
@@ -109,21 +87,8 @@ def bar_separator(config):
padding=config.get('padding_spacer', 4),
)
-def init_notify(qtile):
- if qtile and qtile.theme_instance and qtile.theme_instance.startup_completed:
- try:
- if not notify2.is_initted():
- logger.warning("Initializing Notify2")
- notify2.init("QTileWM")
- except DBusException:
- logger.error(f"Failed to initialize Notify2 (DBus error), retrying later.")
- except Exception:
- logger.error(f"Failed to initialize Notify2 (Generic error), retrying later.")
- else:
- logger.warning(f"Not initializing Notify2 yet, QTile startup not completed.")
-
-def notify(qtile, title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None):
+def notify(title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None):
if image is not None:
notification = Notification(
summary=title, message=content,
@@ -136,14 +101,13 @@ def notify(qtile, title, content, urgency=URGENCY_NORMAL, timeout=5000, image=No
notification.set_timeout(timeout)
notification.set_urgency(urgency)
- init_notify(qtile)
-
try:
- try:
- return notification.show()
- except notify2.UninittedError:
- logger.warning("Notify2 is not initialized")
- except Exception as e:
+ return notification.show()
+ except notify2.UninittedError:
+ logger.warning("Notify2 was uninitialized, initializing...")
+ notify2.init("qtile")
+ return notification.show()
+ except DBusException as e:
logger.warning("Showing notification failed: {}".format(e))
logger.warning(traceback.format_exc())
@@ -161,10 +125,6 @@ def spawn_popup(qtile, x, y, text):
:return: The popup instance
:rtype: Internal
"""
- if qtile.core.name == "x11":
- from libqtile.backend.x11.window import Internal
- else:
- from libqtile.backend.wayland.window import Internal
popup = Internal.create(
qtile, x, y, 100, 100, opacity=1
)
@@ -195,14 +155,14 @@ def test_popups(qtile):
def display_wm_class(qtile):
- window = qtile.current_window if qtile else None
+ window = qtile.currentWindow if qtile else None
if window:
- wm_class = window.get_wm_class() or None
+ wm_class = window.window.get_wm_class() or None
name = window.name
if wm_class:
- notify(qtile=qtile, title="WM_Class of {}".format(name),
+ notify(title="WM_Class of {}".format(name),
content="{}".format(wm_class),
urgency=notify2.URGENCY_CRITICAL)
@@ -229,3 +189,76 @@ def bluetooth_audio_sink():
def bluetooth_audio_connected():
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, *args, **kwargs):
+ super(KuroTopBar, self)._configure(qtile, screen)
+ self.window.handle_EnterNotify = self.handle_enter_notify
+ self.window.handle_LeaveNotify = self.handle_leave_notify
+ self.window.window.set_property("_NET_WM_NAME", "KuroTopBar")
+ self.window.update_name()
+
+ def draw(self):
+ if not self.widgets:
+ return
+ if not self._draw_queued:
+ self.future = self.qtile.call_soon(self._actual_draw)
+ self._draw_queued = True
+
+ def _actual_draw(self):
+ self._draw_queued = False
+ self._resize(self._length, self.widgets)
+ for i in self.widgets:
+ i.draw()
+ if self.widgets:
+ end = i.offset + i.length
+ if end < self._length:
+ if self.horizontal:
+ self.drawer.draw(offsetx=end, width=self._length - end)
+ else:
+ self.drawer.draw(offsety=end, height=self._length - end)
+
+ self.theme.update_visualizers()
+
+ 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()
+
+
diff --git a/kuro/utils/kb_backlight.py b/kuro/utils/kb_backlight.py
index e684735..b818ec4 100644
--- a/kuro/utils/kb_backlight.py
+++ b/kuro/utils/kb_backlight.py
@@ -55,7 +55,7 @@ def handle_focus_change(theme):
window = qtile.currentWindow if qtile else None
if window:
- wm_class = window.get_wm_class() or None
+ wm_class = window.window.get_wm_class() or None
name = window.name
if wm_class:
diff --git a/kuro/utils/layouts.py b/kuro/utils/layouts.py
index 719b526..e74dee6 100644
--- a/kuro/utils/layouts.py
+++ b/kuro/utils/layouts.py
@@ -32,25 +32,29 @@ class KuroFloating(Floating):
# 'sun-awt-X11-XWindowPeer' is a dropdown used in Java application,
# don't reposition it anywhere, let Java app to control it
- cls = client.get_wm_class() or ""
- is_java_dropdown = "sun-awt-X11-XWindowPeer" in cls
+ cls = client.window.get_wm_class() or ''
+ is_java_dropdown = 'sun-awt-X11-XWindowPeer' in cls
if is_java_dropdown:
client.paint_borders(bc, bw)
- client.bring_to_front()
+ client.cmd_bring_to_front()
# alternatively, users may have asked us explicitly to leave the client alone
elif any(m.compare(client) for m in self.no_reposition_rules):
client.paint_borders(bc, bw)
- client.bring_to_front()
+ client.cmd_bring_to_front()
else:
above = False
# We definitely have a screen here, so let's be sure we'll float on screen
- if client.float_x is None or client.float_y is None:
+ try:
+ client.float_x
+ client.float_y
+ except AttributeError:
# this window hasn't been placed before, let's put it in a sensible spot
above = self.compute_client_position(client, screen_rect)
+
client.place(
client.x,
client.y,
@@ -59,6 +63,5 @@ class KuroFloating(Floating):
bw,
bc,
above,
- respect_hints=True,
)
client.unhide()
diff --git a/kuro/utils/widgets.py b/kuro/utils/widgets.py
index aff7f56..442595c 100644
--- a/kuro/utils/widgets.py
+++ b/kuro/utils/widgets.py
@@ -7,19 +7,62 @@ import cairocffi
import iwlib
import netifaces
import psutil
-from libqtile import bar, qtile
+import six
+import unicodedata
+from libqtile import bar, pangocffi
from libqtile.log_utils import logger
+from libqtile.command.base import expose_command
from libqtile.widget import base
from libqtile.widget.base import ORIENTATION_HORIZONTAL
from libqtile.widget.battery import default_icon_path, load_battery, BatteryState
+from libqtile.widget.check_updates import CheckUpdates
+from libqtile.widget.currentlayout import CurrentLayoutIcon
+from libqtile.widget.graph import _Graph
+from libqtile.widget.tasklist import TaskList
from libqtile.widget.wlan import get_status
-from libqtile.widget.groupbox import GroupBox
-from libqtile.command.base import expose_command
+from libqtile.backend.x11.window import Window
from kuro.utils.general import notify, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_UP, BUTTON_MUTE, \
call_process
+class CheckUpdatesYay(CheckUpdates):
+ def __init__(self, **config):
+ super(CheckUpdatesYay, self).__init__(**config)
+ # Override command and output with yay command
+ self.cmd = "yay -Qu".split()
+ self.status_cmd = "yay -Qu --color never".split()
+ self.update_cmd = "sudo yay".split()
+ self.subtr = 0
+
+ def _check_updates(self):
+ #subprocess.check_output(self.update_cmd)
+ res = super(CheckUpdatesYay, self)._check_updates()
+ return res
+
+ 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 DualPaneTextboxBase(base._Widget):
"""
Base class for widgets that are two boxes next to each other both containing text.
@@ -224,6 +267,364 @@ class DualPaneTextboxBase(base._Widget):
return d
+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.'),
+ ('max_chars_per_player', 50, 'Maximum characters of text per player.'),
+ ('ignore_players', '', 'Comma-separated list of players to ignore.')
+ ]
+
+ player_icons = {
+ 'spotify': '',
+ 'vlc': '',
+ 'firefox': '',
+ 'mpv': '',
+ }
+
+ custom_player_data = {
+ 'firefox': {
+ 'showing': False,
+ 'title': '',
+ 'state': Status.STOPPED,
+ }
+ }
+
+ image_urls = {}
+ current_image_url = None
+ player_to_control = None
+
+ def __init__(self, **config):
+ super(MediaWidget, self).__init__(**config)
+ self.add_defaults(MediaWidget.defaults)
+ self.surfaces = {}
+ self.player_to_control = None
+
+ def _player_to_control(self):
+ info = self._get_info()
+ players = {}
+ for player in info.keys():
+ if player not in self.custom_player_data.keys():
+ if info[player][0] in [MediaWidget.Status.PLAYING, MediaWidget.Status.PAUSED]:
+ players[player] = info[player]
+
+ if self.player_to_control is not None and self.player_to_control not in players.keys():
+ self.player_to_control = None
+
+ if self.player_to_control is not None:
+ players = {self.player_to_control: players[self.player_to_control]}
+
+ if len(players.keys()) == 1:
+ player = list(players.keys())[0]
+ self.player_to_control = player
+ return player
+
+ elif len(players) == 0:
+ notify("MediaWidget", "Nothing to control!")
+ else:
+ notify("MediaWidget", "Multiple players to control, I don't know what you want to do!")
+
+ return None
+
+ def button_press(self, x, y, button):
+ if button == BUTTON_LEFT:
+ player = self._player_to_control()
+ if player is not None:
+ command = ["playerctl", "-i", self.ignore_players, "-p", player, "play-pause"]
+ _ = self.call_process(command)
+ notify("MediaWidget", "Toggled {}".format(player))
+ if button == BUTTON_RIGHT:
+ player = self._player_to_control()
+ if player is not None:
+ command = ["playerctl", "-i", self.ignore_players, "-p", player, "next"]
+ _ = self.call_process(command)
+ if button == BUTTON_MIDDLE:
+ # Jump to the screen that the player is on
+ # clients = list(self.bar.qtile.windows_map.values())
+ # logger.warning("{}")
+ pass
+
+ @expose_command()
+ def update_custom_player(self, player_name, data):
+ # Update firefox player
+ if player_name.startswith("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
+ try:
+ result = self.call_process(["playerctl", "-i", self.ignore_players, "-l"])
+ except subprocess.CalledProcessError:
+ result = None
+
+ 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", "-i", self.ignore_players, "-p", player, "status"]
+ cmd_result = self.call_process(command).strip()
+
+ text = "Unknown"
+ if cmd_result in ["Playing", "Paused"]:
+ try:
+ artist = self.call_process(['playerctl', "-i", self.ignore_players, '-p', player, 'metadata', 'artist']).strip()
+ except subprocess.CalledProcessError:
+ artist = None
+ try:
+ title = self.call_process(['playerctl', "-i", self.ignore_players, '-p', player, 'metadata', 'title']).strip()
+ except subprocess.CalledProcessError:
+ title = None
+
+ 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:
+ res = self.on_text_play.format(status[1])
+ elif status[0] == MediaWidget.Status.PAUSED:
+ res = self.on_text_pause.format(status[1])
+ elif status[0] == MediaWidget.Status.STOPPED:
+ res = self.on_text_stop.format(status[1])
+ else:
+ res = "Unknown"
+ res = pangocffi.markup_escape_text(res)
+ res = unicodedata.normalize('NFKD', res)
+ if len(res) > self.max_chars_per_player:
+ res = res[:self.max_chars_per_player] + "..."
+ return res
+
+ def draw(self):
+ super(MediaWidget, self).draw()
+
+ def poll(self):
+ text = []
+ status = self._get_info()
+ if not status:
+ return self.off_text
+ else:
+ for player in status.keys():
+ # Shorten firefox.instance[0-9]+ to just firefox for icon finding
+ if player.startswith("firefox"):
+ player_icon = "firefox"
+ else:
+ player_icon = player
+ icon = self.player_icons.get(player_icon, player_icon)
+ 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
+ fixed_upper_bound = True
+ defaults = [
+ ("graph_color", "FFFFFF.0", "Graph color"),
+ ("fill_color", "FFFFFF.0", "Fill color for linefill graph"),
+ ("border_color", "FFFFFF.0", "Widget border color"),
+ ("border_width", 0, "Widget border width"),
+ ("line_width", 0, "Line width"),
+ ]
+
+ def __init__(self, **config):
+ _Graph.__init__(self, **config)
+ self.add_defaults(AudioVisualizerWidget.defaults)
+
+ self.client = None
+ self.screen = None
+
+ self.old_position = None
+
+ def set_client(self, c, s):
+ self.client = c
+ self.screen = s
+
+ def update_graph(self):
+ if self.client is not None:
+ viz_info = self.info()
+ pos_x = viz_info['offset'] + self.margin_x + self.screen.x
+ pos_y = 0 + self.margin_y + self.screen.y
+ if self.old_position != (pos_x, pos_y):
+ self.old_position = (pos_x, pos_y)
+
+ # Check if a window on this screen is full-screen
+ fullscreen = False
+ for window in self.screen.group.windows:
+ if isinstance(window, Window):
+ if window.fullscreen:
+ fullscreen = True
+ break
+
+ logger.debug("Repositioning {} {} to {}x{}".format(self.client, self.client.window.wid, pos_x, pos_y))
+ self.client.reposition(pos_x, pos_y, above=not fullscreen)
+
+ self.draw()
+
+ def draw(self):
+ self.drawer.clear(self.background or self.bar.background)
+ self.drawer.draw(offsetx=self.offset, width=self.width)
+
+
+class KuroCurrentLayoutIcon(CurrentLayoutIcon):
+ def _get_layout_names(self):
+ names = list(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 set(names)
+
+
+class KuroTaskList(TaskList):
+ defaults = [
+ (
+ 'txt_pinned',
+ 'P ',
+ 'Text representation of the pinned window state. '
+ 'e.g., "P " or "\U0001F5D7 "'
+ ),
+ (
+ 'markup_pinned',
+ None,
+ 'Text markup of the pinned window state. Supports pangomarkup with markup=True.'
+ 'e.g., "{}" or "{}"'
+ ),
+ ]
+
+ def __init__(self, *args, **kwargs):
+ super(KuroTaskList, self).__init__(*args, **kwargs)
+ self.add_defaults(KuroTaskList.defaults)
+
+ def get_taskname(self, window):
+ """
+ Get display name for given window.
+ Depending on its state minimized, maximized and floating
+ appropriate characters are prepended.
+ """
+ state = ''
+ markup_str = self.markup_normal
+
+ # Enforce markup and new string format behaviour when
+ # at least one markup_* option is used.
+ # Mixing non markup and markup may cause problems.
+ if self.markup_minimized or self.markup_maximized\
+ or self.markup_floating or self.markup_focused or self.markup_pinned:
+ enforce_markup = True
+ else:
+ enforce_markup = False
+
+ if window is None:
+ pass
+ elif hasattr(window, "is_static_window") and window.is_static_window:
+ state = self.txt_pinned
+ markup_str = self.markup_pinned
+ elif window.minimized:
+ state = self.txt_minimized
+ markup_str = self.markup_minimized
+ elif window.maximized:
+ state = self.txt_maximized
+ markup_str = self.markup_maximized
+ elif window.floating:
+ state = self.txt_floating
+ markup_str = self.markup_floating
+ elif window is window.group.current_window:
+ markup_str = self.markup_focused
+
+ window_name = window.name if window and window.name else "?"
+
+ # Emulate default widget behavior if markup_str is None
+ if enforce_markup and markup_str is None:
+ markup_str = "%s{}" % (state)
+
+ if markup_str is not None:
+ self.markup = True
+ window_name = pangocffi.markup_escape_text(window_name)
+ return markup_str.format(window_name)
+
+ return "%s%s" % (state, window_name)
+
+
class GPUStatusWidget(base._TextBox):
"""Displays the currently used GPU."""
@@ -254,11 +655,7 @@ class GPUStatusWidget(base._TextBox):
self.icons.update(self.custom_icons)
def _get_info(self):
- try:
- output = self.call_process(self.check_command, shell=True)
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {self.check_command} - {e}")
- output = None
+ output = self.call_process(self.check_command, shell=True)
mode = "nvidia" if "nvidia" in output else "intel" if "intel" in output else "unknown"
return {'error': False, 'mode': mode}
@@ -334,16 +731,14 @@ class GPUStatusWidget(base._TextBox):
if button == BUTTON_LEFT:
try:
next_gpu = self.call_process(self.next_command, shell=True).split(":")[1].strip()
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {self.next_command} - {e}")
except IndexError:
next_gpu = "Unknown"
if self.current_status == "Unknown":
- notify(None, "GPU Status", "The currently used GPU is unknown.\n\nAfter the next login it will be the {} GPU.".format(next_gpu),
+ notify("GPU Status", "The currently used GPU is unknown.\n\nAfter the next login it will be the {} GPU.".format(next_gpu),
image=os.path.join(self.theme_path, "gpu-unknown.png"))
else:
- notify(None, "GPU Status", "The system is currently running on the {} GPU. Press the middle mouse "
+ notify("GPU Status", "The system is currently running on the {} GPU. Press the middle mouse "
"button on this icon to switch GPUs.\n\nAfter the next login it will be the {} GPU.".format(
self.current_status, next_gpu
),
@@ -352,25 +747,38 @@ class GPUStatusWidget(base._TextBox):
if button == BUTTON_MIDDLE:
command = ["optimus-manager", "--no-confirm", "--switch", "auto"]
- try:
- output = self.call_process(command)
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {command} - {e}")
- output = ""
+ output = self.call_process(command)
if "nvidia" in output:
- notify(None, "GPU Switched", "The GPU has been switched from Intel to NVidia.\n"
+ notify("GPU Switched", "The GPU has been switched from Intel to NVidia.\n"
"Please log out and log back in to apply the changes to the session.",
image=os.path.join(self.theme_path, "gpu-nvidia.png"))
elif "intel" in output:
- notify(None, "GPU Switched", "The GPU has been switched from NVidia to Intel.\n"
+ notify("GPU Switched", "The GPU has been switched from NVidia to Intel.\n"
"Please log out and log back in to apply the changes to the session.",
image=os.path.join(self.theme_path, "gpu-intel.png"))
else:
- notify(None, "GPU Switch Error", "I could not determine if the GPU was switched successfully.\n"
+ notify("GPU Switch Error", "I could not determine if the GPU was switched successfully.\n"
"Please log out and log back in to clear up the inconsistency.",
image=os.path.join(self.theme_path, "gpu-unknown.png"))
+class TextSpacerWidget(base._TextBox):
+ """Displays a text separator"""
+ orientations = base.ORIENTATION_HORIZONTAL
+ defaults = [
+ ('spacer', None, 'The character/text to use as separator. Default "|" if None.'),
+ ('color', "#ffffff", "Color of the text."),
+ ]
+
+ def __init__(self, **config):
+ super(TextSpacerWidget, self).__init__("Separator", bar.CALCULATED, **config)
+ self.add_defaults(TextSpacerWidget.defaults)
+ self.text = self.spacer or "|"
+
+ def draw(self):
+ base._TextBox.draw(self)
+
+
class ThermalSensorWidget(DualPaneTextboxBase):
defaults = [
('show_tag', False, 'Show tag sensor'),
@@ -408,11 +816,7 @@ class ThermalSensorWidget(DualPaneTextboxBase):
self.timeout_add(self.update_interval, self.timer_setup)
def _update_values(self):
- try:
- sensors_out = self.call_process(self.get_command())
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {self.get_command()} - {e}")
- return
+ sensors_out = self.call_process(self.get_command())
temperature_values = {}
for name, temp, symbol in self.sensors_temp.findall(sensors_out):
name = name.strip()
@@ -442,7 +846,7 @@ class ThermalSensorWidget(DualPaneTextboxBase):
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
- notify(None, "Temperature Information", "\n".join(
+ notify("Temperature Information", "\n".join(
"{}: {}{}".format(name, *values) for name, values in self.values.items()
))
@@ -495,7 +899,7 @@ class CPUInfoWidget(DualPaneTextboxBase):
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
total = sum([self.cpu_old[0], self.cpu_old[1], self.cpu_old[2], self.cpu_old[3]])
- notify(None, "CPU Information", "user: {} %\nnice: {} %\nsys: {} %\nidle: {} %\ntotal: {} %".format(
+ notify("CPU Information", "user: {} %\nnice: {} %\nsys: {} %\nidle: {} %\ntotal: {} %".format(
math.ceil((self.cpu_old[0] / total) * 100),
math.ceil((self.cpu_old[1] / total) * 100),
math.ceil((self.cpu_old[2] / total) * 100),
@@ -548,7 +952,7 @@ class MemoryInfoWidget(DualPaneTextboxBase):
val['SwapUsed'] = swap.used // 1024 // 1024
if button == BUTTON_LEFT:
- notify(None, "Memory Information", "Memory: {}MB / {}MB\n {}%\nSwap: {}MB / {}MB\n {}%".format(
+ notify("Memory Information", "Memory: {}MB / {}MB\n {}%\nSwap: {}MB / {}MB\n {}%".format(
val['MemUsed'], val['MemTotal'],
math.ceil((mem.used / mem.total) * 100),
val['SwapUsed'], val['SwapTotal'],
@@ -604,7 +1008,7 @@ class DiskIOInfoWidget(DualPaneTextboxBase):
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
- notify(None, "Disk IO Information",
+ notify("Disk IO Information",
"Time that there were IO requests queued for /dev/{}: {} ms".format(self.hdd_device, self.io))
@@ -649,70 +1053,57 @@ class NetworkInfoWidget(DualPaneTextboxBase):
def _update_values(self):
# Wifi
- if self.wireless_interface:
- try:
- essid, quality = get_status(self.wireless_interface)
- status = iwlib.get_iwconfig(self.wireless_interface)
- self.wireless_ips = netifaces.ifaddresses(self.wireless_interface)
- disconnected = essid is None
- percent = math.ceil(((quality or 0) / 70) * 100)
- self.wireless_quality = quality
- self.wireless_signal = percent
- self.wireless_name = essid
- self.wireless_connected = not disconnected
- self.wireless_accesspoint = status.get('Access Point', b'Unknown').decode()
- self.wireless_frequency = status.get('Frequency', b'Unknown').decode()
- self.wireless_ipv4 = self.wireless_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
- self.wireless_ipv6 = self.wireless_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
- self.wireless_mac = self.wireless_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
- except EnvironmentError:
- pass
+ try:
+ essid, quality = get_status(self.wireless_interface)
+ status = iwlib.get_iwconfig(self.wireless_interface)
+ self.wireless_ips = netifaces.ifaddresses(self.wireless_interface)
+ disconnected = essid is None
+ percent = math.ceil((quality / 70) * 100)
+ self.wireless_quality = quality
+ self.wireless_signal = percent
+ self.wireless_name = essid
+ self.wireless_connected = not disconnected
+ self.wireless_accesspoint = status.get('Access Point', b'Unknown').decode()
+ self.wireless_frequency = status.get('Frequency', b'Unknown').decode()
+ self.wireless_ipv4 = self.wireless_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
+ self.wireless_ipv6 = self.wireless_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
+ self.wireless_mac = self.wireless_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
+ except EnvironmentError:
+ pass
# Wired
- if self.wired_interface:
- try:
- self.wired_ips = netifaces.ifaddresses(self.wired_interface)
- self.wired_ipv4 = self.wired_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
- self.wired_ipv6 = self.wired_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
- self.wired_mac = self.wired_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
- command = ["ip", "link", "show", "{}".format(self.wired_interface)]
- try:
- eth_status = call_process(command)
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {command} - {e}")
- return
- m = self.wired_up_regex.search(eth_status)
- if m:
- self.wired_connected = "UP" in m.group(1)
- else:
- self.wired_connected = False
+ try:
+ self.wired_ips = netifaces.ifaddresses(self.wired_interface)
+ self.wired_ipv4 = self.wired_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
+ self.wired_ipv6 = self.wired_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
+ self.wired_mac = self.wired_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
+ eth_status = call_process(["ip", "link", "show", "{}".format(self.wired_interface)])
+ m = self.wired_up_regex.search(eth_status)
+ if m:
+ self.wired_connected = "UP" in m.group(1)
+ else:
+ self.wired_connected = False
- except (EnvironmentError, ValueError):
- pass
+ except (EnvironmentError, ValueError):
+ pass
def update(self):
self._update_values()
self.draw()
def draw(self):
- if self.wireless_interface:
- if self.wireless_connected:
- strength = ""
- if self.wireless_signal < 66:
- strength = ""
- if self.wireless_signal < 33:
- strength = ""
- self.text_left = strength
- else:
- self.text_left = ""
+ if self.wireless_connected:
+ strength = ""
+ if self.wireless_signal < 66:
+ strength = ""
+ if self.wireless_signal < 33:
+ strength = ""
+ self.text_left = strength
else:
- self.text_left = ""
+ self.text_left = ""
- if self.wired_interface:
- if self.wired_connected:
- self.text_right = ""
- else:
- self.text_right = ""
+ if self.wired_connected:
+ self.text_right = ""
else:
self.text_right = ""
@@ -744,9 +1135,9 @@ class NetworkInfoWidget(DualPaneTextboxBase):
wired_text = "Wired: Not connected"
if wifi_text:
- notify(None, title, "{}\n\n{}".format(wifi_text, wired_text))
+ notify(title, "{}\n\n{}".format(wifi_text, wired_text))
else:
- notify(None, title, "\n{}".format(wired_text))
+ notify(title, "\n{}".format(wired_text))
class BatteryInfoWidget(DualPaneTextboxBase):
@@ -822,7 +1213,7 @@ class BatteryInfoWidget(DualPaneTextboxBase):
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
output = subprocess.check_output(self.status_cmd).decode('utf-8')
- notify(None, "Battery Status", output)
+ notify("Battery Status", output)
class VolumeInfoWidget(DualPaneTextboxBase):
@@ -857,8 +1248,7 @@ class VolumeInfoWidget(DualPaneTextboxBase):
else:
cmd = self.status_cmd
mixer_out = self.call_process(cmd.split(" "))
- except subprocess.CalledProcessError as e:
- logger.error(f"Error while calling {cmd} - {e}")
+ except subprocess.CalledProcessError:
return -1
try:
return int(mixer_out)
@@ -902,7 +1292,7 @@ class VolumeInfoWidget(DualPaneTextboxBase):
output = subprocess.check_output(cmd.split(" ")).decode('utf-8')
sink = "Sink {}\n".format(self.pulse_sink) if self.pulse_sink else ""
- notify(None, "Volume Status", sink+output)
+ notify("Volume Status", sink+output)
elif button == BUTTON_RIGHT:
if "{sink}" in self.volume_app:
@@ -953,16 +1343,3 @@ class VolumeInfoWidget(DualPaneTextboxBase):
def run_app(self):
# Emulate button press.
self.button_press(0, 0, BUTTON_RIGHT)
-
-
-class KuroGroupBox(GroupBox):
- @property
- def length(self):
- try:
- return super(KuroGroupBox, self).length
- except AttributeError:
- return 1
-
- @length.setter
- def length(self, length):
- logger.warning(f"Setting groupbox length to {length}")
diff --git a/kuro/utils/windows.py b/kuro/utils/windows.py
index 7370274..6bfd997 100644
--- a/kuro/utils/windows.py
+++ b/kuro/utils/windows.py
@@ -1,11 +1,6 @@
from cairocffi.test_xcb import xcffib
-from libqtile import hook, qtile
-
-if qtile.core.name == "x11":
- from libqtile.backend.x11.window import Window, Static
-else:
- from libqtile.backend.wayland.window import Window, Static
-
+from libqtile import hook
+from libqtile.backend.x11.window import Window, Static
class KuroStatic(Static):
diff --git a/required_packages.txt b/required_packages.txt
index 3462d93..ff3bd88 100644
--- a/required_packages.txt
+++ b/required_packages.txt
@@ -4,34 +4,9 @@ notification-daemon
otf-font-awesome
python-osc
-qtile-extras
-
# /optional/
playerctl
xfce4-screenshooter
xfce4-clipman-plugin
wireless_tools
-
-# Utilities
-kdeconnect # KDE Connect
-vorta # Backup scheduler
-
-# Xorg-only
-picom # Compositor
-xfce4-clipman # Clipboard manager
-dunst # Notification daemon
-xiccd # Color profile manager
-
-# Wayland-only
-xorg-xwayland
-python-pywlroots
-wofi # dmenu replacement
-grim # Screenshot utility
-swappy # Screenshot editor
-slurp # Region selector
-cliphist # Clipboard history
-mako # Notifications daemon
-kanshi # Display hotplugging
-wallutils # Display/wallpaper utilities (lsmon)
-papirus-icon-theme # Icon theme