Files
Modus/modules/dock/components/__init__.py
T

252 lines
9.5 KiB
Python

import json
import os
from fabric.hyprland.service import HyprlandEvent
from fabric.hyprland.widgets import Language, get_hyprland_connection
from fabric.system_tray.widgets import SystemTray
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.datetime import DateTime
from fabric.widgets.label import Label
import config.data as data
import utils.icons as icons
from .applications import Applications
from .battery import Battery
from .controls import Controls
from .indicators import Indicators
from .metrics import Metrics
from .music_player import MusicPlayer
from .workspaces import workspace
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Monkey patch SystemTray to use GTK theme icons
from fabric.system_tray.widgets import SystemTrayItem
def _patched_do_update_properties(self, *_):
"""Enhanced update method that prioritizes GTK theme icons"""
icon_name = self._item.icon_name
if icon_name:
# Use the default GTK icon theme to load the icon
icon_theme = Gtk.IconTheme.get_default()
try:
# Try to load the icon from the GTK theme first
if icon_theme.has_icon(icon_name):
self._image.set_from_icon_name(icon_name, self._icon_size)
else:
# Fallback to original behavior (pixbuf)
pixbuf = self._item.get_preferred_icon_pixbuf(self._icon_size)
if pixbuf is not None:
self._image.set_from_pixbuf(pixbuf)
else:
self._image.set_from_icon_name("image-missing", self._icon_size)
except Exception:
# Fallback to original behavior on any error
pixbuf = self._item.get_preferred_icon_pixbuf(self._icon_size)
if pixbuf is not None:
self._image.set_from_pixbuf(pixbuf)
else:
self._image.set_from_icon_name("image-missing", self._icon_size)
else:
# No icon name, use original behavior
pixbuf = self._item.get_preferred_icon_pixbuf(self._icon_size)
if pixbuf is not None:
self._image.set_from_pixbuf(pixbuf)
else:
self._image.set_from_icon_name("image-missing", self._icon_size)
# Set tooltip (same as original)
tooltip = self._item.tooltip
self.set_tooltip_markup(
tooltip.description or tooltip.title or self._item.title.title() if self._item.title else "Unknown"
)
return
# Apply the patch
SystemTrayItem.do_update_properties = _patched_do_update_properties
class DockComponents(Box):
def __init__(self, orientation_val="h", dock_instance=None, **kwargs):
super().__init__(
name="dock-components", orientation=orientation_val, spacing=8, **kwargs
)
self.dock_instance = dock_instance
# Create applications with dock reference
self.applications = Applications(
orientation_val=orientation_val, dock_instance=dock_instance
)
self.connection = get_hyprland_connection()
self.lang_label = Label(name="lang-label")
self.language = Button(
name="language", h_align="center", v_align="center", child=self.lang_label
)
self.on_language_switch()
self.connection.connect("event::activelayout", self.on_language_switch)
# Initialize component visibility from data
self.component_visibility = data.DOCK_COMPONENTS_VISIBILITY
# Create components
self.workspaces = workspace
self.metrics = Metrics()
self.battery = Battery()
self.date_time = DateTime(
name="date-time",
formatters=["%I:%M"] if not data.VERTICAL else ["%I\n%M"],
h_align="center" if not data.VERTICAL else "fill",
v_align="center",
h_expand=True,
v_expand=True,
)
self.controls = Controls()
self.indicators = Indicators()
self.music_player = MusicPlayer()
self.systray = SystemTray(
icon_size=18,
spacing=4,
name="tray",
orientation="h" if not data.VERTICAL else "v",
)
# Create list of components to add
self.themed_children = [
self.workspaces,
self.metrics,
self.controls,
self.applications,
self.indicators,
self.battery,
self.music_player,
self.date_time,
self.language,
self.systray,
]
# Add each child individually
for child in self.themed_children:
self.add(child)
# Apply initial visibility
self.apply_component_props()
# Apply invert style for specific themes and positions
should_invert = data.DOCK_THEME in ["Dense", "Edge"] or (
data.DOCK_THEME == "Pills" and data.DOCK_POSITION in ["Left", "Right", "Top"]
)
if should_invert:
for child in self.themed_children:
if hasattr(child, "add_style_class"):
child.add_style_class("invert")
def apply_component_props(self):
components = {
"workspace": self.workspaces,
"metrics": self.metrics,
"battery": self.battery,
"date_time": self.date_time,
"controls": self.controls,
"indicators": self.indicators,
"music_player": self.music_player,
"systray": self.systray,
"applications": self.applications,
"language": self.language,
}
for component_name, widget in components.items():
if component_name in self.component_visibility:
visibility = self.component_visibility[component_name]
# Special handling for applications component - let it manage its own visibility
if component_name == "applications":
# Only apply config visibility if the component has content
if hasattr(widget, "_update_visibility_based_on_content"):
widget._update_visibility_based_on_content()
else:
widget.set_visible(visibility)
# Special handling for music player - let it manage its own visibility based on media state
elif component_name == "music_player":
# Music player manages its own visibility based on config and media state
# Don't override its visibility here, let it handle it internally
pass
# Special handling for battery - let it manage its own visibility based on config and battery state
elif component_name == "battery":
# Battery component manages its own visibility based on config and battery presence
# Don't override its visibility here, let it handle it internally
pass
else:
widget.set_visible(visibility)
def toggle_component_visibility(self, component_name):
components = {
"workspace": self.workspaces,
"metrics": self.metrics,
"battery": self.battery,
"date_time": self.date_time,
"controls": self.controls,
"indicators": self.indicators,
"music_player": self.music_player,
"systray": self.systray,
"applications": self.applications,
"language": self.language,
}
if component_name in components and component_name in self.component_visibility:
self.component_visibility[component_name] = not self.component_visibility[
component_name
]
# Special handling for music player - update the global config and let it handle its own visibility
if component_name == "music_player":
# Update the global config data
import config.data as data
data.DOCK_COMPONENTS_VISIBILITY[component_name] = self.component_visibility[component_name]
# Let the music player update its own visibility based on the new config
components[component_name]._update_display()
else:
components[component_name].set_visible(
self.component_visibility[component_name]
)
config_file = get_relative_path("../../../config/assets/config.json")
if os.path.exists(config_file):
try:
with open(config_file, "r") as f:
config = json.load(f)
config[f"dock_{component_name}_visible"] = (
self.component_visibility[component_name]
)
with open(config_file, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error updating config file: {e}")
return self.component_visibility[component_name]
return None
def on_language_switch(self, _=None, event: HyprlandEvent = None):
lang_data = (
event.data[1]
if event and event.data and len(event.data) > 1
else Language().get_label()
)
self.language.set_tooltip_text(lang_data)
if not data.VERTICAL:
self.lang_label.set_label(lang_data[:2].lower())
else:
self.lang_label.add_style_class("icon")
self.lang_label.set_markup(icons.keyboard)