Unify configs so we can get rid of all those branches per machine. Also Wayland changes for Violet

This commit is contained in:
Kevin Alberts 2025-07-25 17:39:36 +02:00
parent 5a0041e7d5
commit 6dd362247e
10 changed files with 274 additions and 105 deletions

View file

@ -27,6 +27,7 @@
# Import Theme # Import Theme
from libqtile import hook from libqtile import hook
from libqtile.log_utils import logger from libqtile.log_utils import logger
from kuro.utils import load_config_class
try: try:
from kuro.theme import Kuro from kuro.theme import Kuro
@ -40,20 +41,15 @@ except ImportError as e:
Kuro = None Kuro = None
raise ImportError("Could not load theme Config or BaseTheme! Error: {}".format(e)) raise ImportError("Could not load theme Config or BaseTheme! Error: {}".format(e))
# Import theme configuration
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))
# Import theme configuration
Config = load_config_class()
if Config is None:
raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
try: try:
logger.warning("Initializing theme...") logger.warning("Initializing theme...")
logger.warning(f"Using config variables for '{Config.get('config_name', '????')}'")
# Initialize the Theme # Initialize the Theme
Theme.initialize() Theme.initialize()
logger.warning("Initialize done") logger.warning("Initialize done")
@ -114,6 +110,8 @@ except Exception as e:
def main(qtile): def main(qtile):
Config.initialize(qtile)
# set logging level # set logging level
if Config.get('debug', False): if Config.get('debug', False):
if Config.get('verbose', False): if Config.get('verbose', False):

View file

@ -1,3 +1,5 @@
import time
from libqtile import layout as libqtile_layout, layout, bar, widget from libqtile import layout as libqtile_layout, layout, bar, widget
from libqtile.lazy import lazy from libqtile.lazy import lazy
from libqtile.config import Key, Group, Screen, Drag, Click, Match from libqtile.config import Key, Group, Screen, Drag, Click, Match
@ -7,13 +9,21 @@ from libqtile.log_utils import logger
class BaseConfig: class BaseConfig:
config_name = "KuroBase"
@classmethod @classmethod
def get(cls, key, default): def get(cls, key, default):
if hasattr(cls, key): if hasattr(cls, key):
return cls.__dict__[key] return getattr(cls, key)
#return cls.__dict__[key]
else: else:
return default return default
@classmethod
def initialize(cls, qtile):
# Can do extra initialization based on qtile instance here
pass
class BaseTheme: class BaseTheme:
# Changing variables initialized by function # Changing variables initialized by function
@ -45,7 +55,7 @@ class BaseTheme:
auto_fullscreen = True auto_fullscreen = True
focus_on_window_activation = "smart" focus_on_window_activation = "smart"
extensions = [] extensions = []
reconfigure_screens = True reconfigure_screens = False
# XXX: Gasp! We're lying here. In fact, nobody really uses or cares about this # 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 # string besides java UI toolkits; you can see several discussions on the
@ -60,6 +70,9 @@ class BaseTheme:
# 'export _JAVA_AWT_WM_NONREPARENTING=1' # 'export _JAVA_AWT_WM_NONREPARENTING=1'
wmname = "LG3D" wmname = "LG3D"
def __init__(self):
self.startup_time = time.time()
def initialize(self): def initialize(self):
logger.info("Initializing widget defaults...") logger.info("Initializing widget defaults...")
self.widget_defaults = self.init_widget_defaults() self.widget_defaults = self.init_widget_defaults()

View file

@ -1,8 +1,11 @@
from kuro.base import BaseConfig from kuro.base import BaseConfig
from libqtile.log_utils import logger
# Config variables used in the main configuration # Config variables used in the main configuration
class Config(BaseConfig): class Config(BaseConfig):
config_name = "KuroGeneral"
# Show debug bar and messages # Show debug bar and messages
debug = False debug = False
verbose = False verbose = False
@ -24,7 +27,7 @@ class Config(BaseConfig):
# Default Applications # Default Applications
app_terminal = "ghostty" app_terminal = "ghostty"
app_launcher = "wofi --show drun,run" app_launcher = "wofi --show run,drun"
file_manager = "thunar" file_manager = "thunar"
visualizer_app = "glava" visualizer_app = "glava"
web_browser = "firefox" web_browser = "firefox"
@ -38,15 +41,24 @@ class Config(BaseConfig):
{'group': "", 'command': ["thunderbird"]}, {'group': "", 'command': ["thunderbird"]},
{'group': "", 'command': ["spotify"]}, {'group': "", 'command': ["spotify"]},
] ]
apps_autostart = [ apps_autostart = {
# ["ulauncher", "--hide-window", "--no-window-shadow"], # App launcher background daemon 'common': [
["mako"], # Notification daemon ["/usr/lib/kdeconnectd"], # KDE Connect daemon
["kanshi"], # Display hotplug ["kdeconnect-indicator"], # KDE Connect tray
["wl-paste", "--watch", "cliphist", "store"], # Clipboard manager ["vorta"], # Vorta backup scheduler
["/usr/lib/kdeconnectd"], # KDE Connect daemon ],
["kdeconnect-indicator"], # KDE Connect tray 'x11': [
["vorta"], # Vorta backup scheduler ["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
]
}
# Keyboard commands # Keyboard commands
cmd_media_play = "playerctl -i kdeconnect play-pause" cmd_media_play = "playerctl -i kdeconnect play-pause"
@ -129,8 +141,8 @@ class Config(BaseConfig):
# Thermal indicator variables # Thermal indicator variables
thermal_threshold = 75 thermal_threshold = 75
thermal_sensor = "Tdie" thermal_sensor = "Package id 0"
thermal_chip = "zenpower-pci-00c3" thermal_chip = "coretemp-isa-0000"
# CPU graph variables # CPU graph variables
cpu_graph_colour = '#ff0000' cpu_graph_colour = '#ff0000'
@ -149,7 +161,7 @@ class Config(BaseConfig):
wifi_interface = "wifi0" wifi_interface = "wifi0"
wifi_theme_path = "/home/kevin/.config/qtile/kuro/resources/wifi" wifi_theme_path = "/home/kevin/.config/qtile/kuro/resources/wifi"
wifi_update_interval = 5 wifi_update_interval = 5
wired_interface = "br1" wired_interface = "eth0"
# GPU variables # GPU variables
gpu_theme_path = "/home/kevin/.config/qtile/kuro/resources/gpu" gpu_theme_path = "/home/kevin/.config/qtile/kuro/resources/gpu"
@ -158,10 +170,7 @@ class Config(BaseConfig):
volume_font = "Noto Sans" volume_font = "Noto Sans"
volume_fontsize = 11 volume_fontsize = 11
volume_theme_path = "/home/kevin/.config/qtile/kuro/resources/volume" volume_theme_path = "/home/kevin/.config/qtile/kuro/resources/volume"
volume_pulse_sinks = [ volume_pulse_sinks = []
"alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo-output",
"alsa_output.pci-0000_0d_00.4.analog-stereo",
]
volume_is_bluetooth_icon = False volume_is_bluetooth_icon = False
volume_update_interval = 0.2 volume_update_interval = 0.2
@ -200,3 +209,13 @@ class Config(BaseConfig):
# Comma-separated list of ignored players in the media widget # Comma-separated list of ignored players in the media widget
media_ignore_players = "kdeconnect" 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"

45
kuro/config/aria.py Normal file
View file

@ -0,0 +1,45 @@
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"

22
kuro/config/meconopsis.py Normal file
View file

@ -0,0 +1,22 @@
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",
]

26
kuro/config/violet.py Normal file
View file

@ -0,0 +1,26 @@
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",
]

View file

@ -1,6 +1,7 @@
import json import json
import os import os
import random import random
import time
import datetime import datetime
import socket import socket
import subprocess import subprocess
@ -39,14 +40,11 @@ from kuro.utils import layouts as kuro_layouts
logger.warning("Importing configuration...") logger.warning("Importing configuration...")
try: from kuro.utils import load_config_class
from kuro.config import Config Config = load_config_class()
except ImportError: if Config is None:
try: raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
from kuro.baseconfig import BaseConfig as Config Config.initialize(qtile)
except ImportError:
Config = None
raise ImportError("Could not load theme Config or BaseConfig!")
logger.warning("Imports done") logger.warning("Imports done")
@ -58,9 +56,6 @@ class Kuro(BaseTheme):
# Screen count # Screen count
num_screens = 0 num_screens = 0
# Top bars
topbars = []
# Static windows # Static windows
static_windows = [] static_windows = []
@ -310,9 +305,6 @@ class Kuro(BaseTheme):
Config.bar_background = colors['color1'] Config.bar_background = colors['color1']
def reinit_screens(self): def reinit_screens(self):
# Re-initalize bars
self.topbars.clear()
# TODO: Move backend check into utils method # TODO: Move backend check into utils method
if qtile.core.name == "x11": if qtile.core.name == "x11":
self.num_screens = max(1, utils.get_screen_count()) self.num_screens = max(1, utils.get_screen_count())
@ -320,18 +312,19 @@ class Kuro(BaseTheme):
self.num_screens = max(1, len(qtile.core.get_screen_info())) self.num_screens = max(1, len(qtile.core.get_screen_info()))
logger.warning(f"Detected {self.num_screens} screens.") logger.warning(f"Detected {self.num_screens} screens.")
# TODO: If i get the double topbar issue, this might be the
# cause; creating new screens on reinit...
screens = []
for x in range(self.num_screens): for x in range(self.num_screens):
logger.warning("Initializing bars for screen {}".format(x)) logger.warning("Reconfiguring bars for screen {}".format(x))
topbar = self.build_bar_for_screen(x)
self.topbars.append(topbar)
screens.append(Screen(top=topbar))
self.screens.clear() try:
for s in screens: screen = self.screens[x]
self.screens.append(s) 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): def update_keys(self):
logger.warning("Updating keys") logger.warning("Updating keys")
@ -606,7 +599,6 @@ class Kuro(BaseTheme):
# Update color scheme # Update color scheme
self.update_colorscheme() self.update_colorscheme()
self.startup_completed = True
def callback_startup_complete(self, *args, **kwargs): def callback_startup_complete(self, *args, **kwargs):
logger.warning("Callback Startup Complete") logger.warning("Callback Startup Complete")
@ -617,14 +609,20 @@ class Kuro(BaseTheme):
# Update color scheme # Update color scheme
self.update_colorscheme() self.update_colorscheme()
# Setup XDG Desktop Portal # Setup XDG Desktop Portal on Wayland
self.setup_xdg_desktop_portal() if qtile.core.name == "wayland":
self.setup_xdg_desktop_portal()
# After first startup is complete, autostart configured apps # After first startup is complete, autostart configured apps
logger.warning("Autostarting apps...") logger.warning("Autostarting apps...")
for app in Config.get("apps_autostart", []): for category in Config.get("apps_autostart", {}).keys():
logger.warning(f"Starting '{app}'...") if qtile.core.name == category or category == "common":
utils.execute_once(app) 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", []): for app in Config.get("apps_autostart_group", []):
if all(x in app.keys() for x in ["group", "command"]): if all(x in app.keys() for x in ["group", "command"]):
@ -634,6 +632,9 @@ class Kuro(BaseTheme):
logger.warning(f"Invalid app in 'apps_autostart_group', " logger.warning(f"Invalid app in 'apps_autostart_group', "
f"must have 'group' and 'command' keys: {app}...") f"must have 'group' and 'command' keys: {app}...")
logger.warning("Autostart complete") 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): def callback_client_managed(self, *args, **kwargs):
client: Optional[Window] = args[0] if len(args) > 0 else None client: Optional[Window] = args[0] if len(args) > 0 else None
@ -667,9 +668,14 @@ class Kuro(BaseTheme):
del client.is_static_window del client.is_static_window
self.static_windows.remove(client) self.static_windows.remove(client)
def callback_screens_reconfigured(self, *args, **kwargs): def callback_screen_change(self, *args, **kwargs):
logger.warning(f"Re-configuring screens!") logger.warning(f"Screen configuration changed, reinitializing screens")
self.reinit_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.set_wallpaper(self.current_wallpaper)
self.update_colorscheme() self.update_colorscheme()

View file

@ -0,0 +1,30 @@
import socket
import importlib
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:
pass
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:
pass
return None

View file

@ -649,62 +649,70 @@ class NetworkInfoWidget(DualPaneTextboxBase):
def _update_values(self): def _update_values(self):
# Wifi # Wifi
try: if self.wireless_interface:
essid, quality = get_status(self.wireless_interface) try:
status = iwlib.get_iwconfig(self.wireless_interface) essid, quality = get_status(self.wireless_interface)
self.wireless_ips = netifaces.ifaddresses(self.wireless_interface) status = iwlib.get_iwconfig(self.wireless_interface)
disconnected = essid is None self.wireless_ips = netifaces.ifaddresses(self.wireless_interface)
percent = math.ceil(((quality or 0) / 70) * 100) disconnected = essid is None
self.wireless_quality = quality percent = math.ceil(((quality or 0) / 70) * 100)
self.wireless_signal = percent self.wireless_quality = quality
self.wireless_name = essid self.wireless_signal = percent
self.wireless_connected = not disconnected self.wireless_name = essid
self.wireless_accesspoint = status.get('Access Point', b'Unknown').decode() self.wireless_connected = not disconnected
self.wireless_frequency = status.get('Frequency', b'Unknown').decode() self.wireless_accesspoint = status.get('Access Point', b'Unknown').decode()
self.wireless_ipv4 = self.wireless_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr'] self.wireless_frequency = status.get('Frequency', b'Unknown').decode()
self.wireless_ipv6 = self.wireless_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr'] self.wireless_ipv4 = self.wireless_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
self.wireless_mac = self.wireless_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr'] self.wireless_ipv6 = self.wireless_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
except EnvironmentError: self.wireless_mac = self.wireless_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
pass except EnvironmentError:
pass
# Wired # Wired
try: if self.wired_interface:
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: try:
eth_status = call_process(command) self.wired_ips = netifaces.ifaddresses(self.wired_interface)
except subprocess.CalledProcessError as e: self.wired_ipv4 = self.wired_ips.get(netifaces.AF_INET, [{'addr': ""}])[0]['addr']
logger.error(f"Error while calling {command} - {e}") self.wired_ipv6 = self.wired_ips.get(netifaces.AF_INET6, [{'addr': ""}])[0]['addr']
return self.wired_mac = self.wired_ips.get(netifaces.AF_LINK, [{'addr': ""}])[0]['addr']
m = self.wired_up_regex.search(eth_status) command = ["ip", "link", "show", "{}".format(self.wired_interface)]
if m: try:
self.wired_connected = "UP" in m.group(1) eth_status = call_process(command)
else: except subprocess.CalledProcessError as e:
self.wired_connected = False 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
except (EnvironmentError, ValueError): except (EnvironmentError, ValueError):
pass pass
def update(self): def update(self):
self._update_values() self._update_values()
self.draw() self.draw()
def draw(self): def draw(self):
if self.wireless_connected: if self.wireless_interface:
strength = "" if self.wireless_connected:
if self.wireless_signal < 66: strength = ""
strength = "" if self.wireless_signal < 66:
if self.wireless_signal < 33: strength = ""
strength = "" if self.wireless_signal < 33:
self.text_left = strength strength = ""
self.text_left = strength
else:
self.text_left = ""
else: else:
self.text_left = "" self.text_left = ""
if self.wired_connected: if self.wired_interface:
self.text_right = "" if self.wired_connected:
self.text_right = ""
else:
self.text_right = ""
else: else:
self.text_right = "" self.text_right = ""

View file

@ -4,6 +4,8 @@ notification-daemon
otf-font-awesome otf-font-awesome
python-osc python-osc
qtile-extras
# /optional/ # /optional/
playerctl playerctl