✨ feat(dock/systemtray): add custom SystemTray component with GTK theme integration and Gray watcher
This commit is contained in:
4 files changed
+195
-76
No files matched your search
@@ -11,3 +11,5 @@ config/assets/screentime.json
|
||||
config/hypr/modus.conf
|
||||
config/hypr/colors.conf
|
||||
styles/colors.css
|
||||
gray/*
|
||||
gray
|
||||
+21
-21
@@ -1,27 +1,27 @@
|
||||
$wallpaper = /home/sankalp/Pictures/wallpapers/77.jpg
|
||||
$background = 121318
|
||||
$foreground = e2e2e9
|
||||
$wallpaper = /home/sankalp/Pictures/wallpapers/5.png
|
||||
$background = 1a110f
|
||||
$foreground = f1dfd9
|
||||
|
||||
$primary = afc6ff
|
||||
$secondary = bfc6dc
|
||||
$tertiary = dfbbde
|
||||
$surface = 121318
|
||||
$surface_bright = 38393e
|
||||
$outline = 8f9099
|
||||
$primary = ffb59c
|
||||
$secondary = e7bdb0
|
||||
$tertiary = d6c68d
|
||||
$surface = 1a110f
|
||||
$surface_bright = 423733
|
||||
$outline = a08d87
|
||||
$error = ff5945
|
||||
|
||||
$shadow = 000000
|
||||
|
||||
$red = ffb2b9
|
||||
$green = 95d5a7
|
||||
$yellow = b8cf84
|
||||
$blue = b7c4ff
|
||||
$magenta = e4b7f3
|
||||
$cyan = 82d3e2
|
||||
$red = ffb4a2
|
||||
$green = b7d085
|
||||
$yellow = dec56e
|
||||
$blue = cebdfe
|
||||
$magenta = fcb0d5
|
||||
$cyan = 84d5c4
|
||||
$white = 82d3e0
|
||||
$red_dim = ff4c5c
|
||||
$green_dim = 4cb86a
|
||||
$yellow_dim = 8baa43
|
||||
$blue_dim = 5170ff
|
||||
$magenta_dim = c360e4
|
||||
$cyan_dim = 30b5ce
|
||||
$red_dim = ff623c
|
||||
$green_dim = 89ad42
|
||||
$yellow_dim = bb9b2b
|
||||
$blue_dim = 8359fc
|
||||
$magenta_dim = f84ea1
|
||||
$cyan_dim = 3eb59c
|
||||
@@ -6,7 +6,6 @@ import os
|
||||
import gi
|
||||
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
|
||||
@@ -23,59 +22,12 @@ from .indicators import Indicators
|
||||
from .metrics import Metrics
|
||||
from .music_player import MusicPlayer
|
||||
from .workspaces import workspace
|
||||
from .systemtray import SystemTray
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
# Monkey patch SystemTray to use GTK theme icons
|
||||
|
||||
|
||||
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__(
|
||||
@@ -115,12 +67,7 @@ class DockComponents(Box):
|
||||
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",
|
||||
)
|
||||
self.systray = SystemTray()
|
||||
|
||||
# Create list of components to add
|
||||
self.themed_children = [
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import config.data as data
|
||||
from gi.repository import Gdk, GdkPixbuf, GLib, Gray, Gtk
|
||||
from fabric.widgets.box import Box
|
||||
import logging
|
||||
import gi
|
||||
|
||||
gi.require_version("Gray", "0.1")
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SystemTray(Box):
|
||||
def __init__(
|
||||
self, pixel_size: int = 16, refresh_interval: int = 1, **kwargs
|
||||
) -> None:
|
||||
orientation = (
|
||||
Gtk.Orientation.HORIZONTAL
|
||||
if not data.VERTICAL
|
||||
else Gtk.Orientation.VERTICAL
|
||||
)
|
||||
super().__init__(name="tray", orientation=orientation, spacing=4, **kwargs)
|
||||
self.enabled = True
|
||||
super().set_visible(False)
|
||||
self.pixel_size = pixel_size
|
||||
self.refresh_interval = refresh_interval
|
||||
|
||||
self.buttons_by_id = {}
|
||||
self.items_by_id = {}
|
||||
|
||||
self.watcher = Gray.Watcher()
|
||||
self.watcher.connect("item-added", self.on_watcher_item_added)
|
||||
|
||||
GLib.timeout_add_seconds(self.refresh_interval, self._refresh_all_items)
|
||||
|
||||
def set_visible(self, visible: bool):
|
||||
self.enabled = visible
|
||||
self._update_visibility()
|
||||
|
||||
def _update_visibility(self):
|
||||
has = len(self.get_children()) > 0
|
||||
super().set_visible(self.enabled and has)
|
||||
|
||||
def _get_item_pixbuf(self, item: Gray.Item) -> GdkPixbuf.Pixbuf:
|
||||
try:
|
||||
pm = Gray.get_pixmap_for_pixmaps(item.get_icon_pixmaps(), self.pixel_size)
|
||||
if pm:
|
||||
return pm.as_pixbuf(self.pixel_size, GdkPixbuf.InterpType.HYPER)
|
||||
|
||||
name = item.get_icon_name()
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
path = item.get_icon_theme_path()
|
||||
if path:
|
||||
theme.prepend_search_path(path)
|
||||
return theme.load_icon(
|
||||
name, self.pixel_size, Gtk.IconLookupFlags.FORCE_SIZE
|
||||
)
|
||||
except GLib.Error as e:
|
||||
logger.debug(f"Icon load error {e}")
|
||||
return Gtk.IconTheme.get_default().load_icon(
|
||||
"image-missing", self.pixel_size, Gtk.IconLookupFlags.FORCE_SIZE
|
||||
)
|
||||
|
||||
def _refresh_item_ui(self, identifier: str, item: Gray.Item, button: Gtk.Button):
|
||||
pixbuf = self._get_item_pixbuf(item)
|
||||
img = button.get_image()
|
||||
if isinstance(img, Gtk.Image):
|
||||
img.set_from_pixbuf(pixbuf)
|
||||
else:
|
||||
new = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
button.set_image(new)
|
||||
new.show()
|
||||
tip = None
|
||||
if hasattr(item, "get_tooltip_text"):
|
||||
tip = item.get_tooltip_text()
|
||||
elif hasattr(item, "get_title"):
|
||||
tip = item.get_title()
|
||||
if tip:
|
||||
button.set_tooltip_text(tip)
|
||||
else:
|
||||
button.set_has_tooltip(False)
|
||||
|
||||
def _refresh_all_items(self) -> bool:
|
||||
for ident, item in self.items_by_id.items():
|
||||
btn = self.buttons_by_id.get(ident)
|
||||
if btn:
|
||||
self._refresh_item_ui(ident, item, btn)
|
||||
return True
|
||||
|
||||
def on_watcher_item_added(self, _, identifier: str):
|
||||
item = self.watcher.get_item_for_identifier(identifier)
|
||||
if not item:
|
||||
return
|
||||
|
||||
if identifier in self.buttons_by_id:
|
||||
self.buttons_by_id[identifier].destroy()
|
||||
del self.buttons_by_id[identifier]
|
||||
del self.items_by_id[identifier]
|
||||
|
||||
btn = self.do_bake_item_button(item)
|
||||
self.buttons_by_id[identifier] = btn
|
||||
self.items_by_id[identifier] = item
|
||||
|
||||
item.connect(
|
||||
"notify::icon-pixmaps",
|
||||
lambda itm, pspec: self._refresh_item_ui(identifier, itm, btn),
|
||||
)
|
||||
item.connect(
|
||||
"notify::icon-name",
|
||||
lambda itm, pspec: self._refresh_item_ui(identifier, itm, btn),
|
||||
)
|
||||
|
||||
try:
|
||||
item.connect(
|
||||
"updated", lambda itm: self._refresh_item_ui(identifier, itm, btn)
|
||||
)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
item.connect(
|
||||
"removed", lambda itm: self.on_item_instance_removed(identifier, itm)
|
||||
)
|
||||
|
||||
self.add(btn)
|
||||
btn.show_all()
|
||||
self._update_visibility()
|
||||
|
||||
def do_bake_item_button(self, item: Gray.Item) -> Gtk.Button:
|
||||
btn = Gtk.Button()
|
||||
btn.connect("button-press-event", lambda b, e: self.on_button_click(b, item, e))
|
||||
img = Gtk.Image.new_from_pixbuf(self._get_item_pixbuf(item))
|
||||
btn.set_image(img)
|
||||
tip = (
|
||||
item.get_tooltip_text()
|
||||
if hasattr(item, "get_tooltip_text")
|
||||
else getattr(item, "get_title", lambda: None)()
|
||||
)
|
||||
if tip:
|
||||
btn.set_tooltip_text(tip)
|
||||
return btn
|
||||
|
||||
def on_item_instance_removed(self, identifier: str, removed_item: Gray.Item):
|
||||
if self.items_by_id.get(identifier) is removed_item:
|
||||
btn = self.buttons_by_id.pop(identifier, None)
|
||||
self.items_by_id.pop(identifier, None)
|
||||
if btn:
|
||||
btn.destroy()
|
||||
self._update_visibility()
|
||||
|
||||
def on_button_click(
|
||||
self, button: Gtk.Button, item: Gray.Item, event: Gdk.EventButton
|
||||
):
|
||||
if event.button == Gdk.BUTTON_PRIMARY:
|
||||
try:
|
||||
item.activate(int(event.x_root), int(event.y_root))
|
||||
except Exception as e:
|
||||
logger.error(f"Activate error: {e}")
|
||||
elif event.button == Gdk.BUTTON_SECONDARY:
|
||||
menu = getattr(item, "get_menu", lambda: None)()
|
||||
if isinstance(menu, Gtk.Menu):
|
||||
menu.popup_at_widget(
|
||||
button, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, event
|
||||
)
|
||||
else:
|
||||
cm = getattr(item, "context_menu", None)
|
||||
if cm:
|
||||
try:
|
||||
cm(int(event.x_root), int(event.y_root))
|
||||
except Exception as e:
|
||||
logger.error(f"ContextMenu error: {e}")
|
||||
Reference in new issue
Block a user