forked from Axenide/Ax-Shell
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystemtray.py
More file actions
154 lines (132 loc) · 5.71 KB
/
systemtray.py
File metadata and controls
154 lines (132 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import gi
gi.require_version("Gray", "0.1")
import logging
import os
from fabric.widgets.box import Box
from gi.repository import Gdk, GdkPixbuf, GLib, Gray, Gtk
import config.data as data
logger = logging.getLogger(__name__)
class SystemTray(Box):
def __init__(self, pixel_size: int = 20, **kwargs) -> None:
orientation = Gtk.Orientation.HORIZONTAL if not data.VERTICAL else Gtk.Orientation.VERTICAL
super().__init__(
name="systray",
orientation=orientation,
spacing=8,
**kwargs
)
self.enabled = True
super().set_visible(False)
self.pixel_size = pixel_size
self.buttons_by_id = {}
self.items_by_id = {}
self.watcher = Gray.Watcher()
self.watcher.connect("item-added", self.on_watcher_item_added)
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()
# If IconName is a file path, prioritize loading directly from the file
if name and os.path.exists(name):
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(
name, self.pixel_size, self.pixel_size, True
)
except Exception as e:
# The file path exists but loading fails, falling back to theme search
logger.debug(
f"Load icon from file failed: {e}; fallback to theme for '{name}'"
)
theme = Gtk.IconTheme.new()
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, 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 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(itm, btn))
item.connect("notify::icon-name",
lambda itm, pspec: self._refresh_item_ui(itm, btn))
try:
item.connect("icon-changed", lambda itm: self._refresh_item_ui(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}")