|
0 |
#!/usr/bin/python3
|
|
1 |
|
|
2 |
import locale
|
|
3 |
import gettext
|
|
4 |
import os
|
|
5 |
import sys
|
|
6 |
import setproctitle
|
|
7 |
|
|
8 |
import gi
|
|
9 |
gi.require_version("Gtk", "3.0")
|
|
10 |
gi.require_version("XApp", "1.0")
|
|
11 |
gi.require_version('MatePanelApplet', '4.0')
|
|
12 |
from gi.repository import Gtk, GdkPixbuf, Gdk, GObject, Gio, XApp, GLib, MatePanelApplet
|
|
13 |
|
|
14 |
# Rename the process
|
|
15 |
setproctitle.setproctitle('mate-xapp-status-applet')
|
|
16 |
|
|
17 |
# i18n
|
|
18 |
gettext.install("xapp", "@locale@")
|
|
19 |
locale.bindtextdomain("xapp", "@locale@")
|
|
20 |
locale.textdomain("xapp")
|
|
21 |
|
|
22 |
INDICATOR_BOX_BORDER = 1
|
|
23 |
INDICATOR_BOX_BORDER_COMP = INDICATOR_BOX_BORDER + 1
|
|
24 |
ICON_SIZE_REDUCTION = (INDICATOR_BOX_BORDER * 2)
|
|
25 |
ICON_SPACING = 5
|
|
26 |
VISIBLE_LABEL_MARGIN = 5 # When an icon has a label, add a margin between icon and label
|
|
27 |
|
|
28 |
statusicon_css_string = """
|
|
29 |
.statuswidget {
|
|
30 |
border: none;
|
|
31 |
padding-top: 0;
|
|
32 |
padding-left: 4px;
|
|
33 |
padding-bottom: 0;
|
|
34 |
padding-right: 4px;
|
|
35 |
}
|
|
36 |
"""
|
|
37 |
|
|
38 |
def translate_applet_orientation_to_xapp(mate_applet_orientation):
|
|
39 |
# wtf...mate panel's orientation is.. the direction to center of monitor?
|
|
40 |
if mate_applet_orientation == MatePanelApplet.AppletOrient.UP:
|
|
41 |
return Gtk.PositionType.BOTTOM
|
|
42 |
elif mate_applet_orientation == MatePanelApplet.AppletOrient.DOWN:
|
|
43 |
return Gtk.PositionType.TOP
|
|
44 |
elif mate_applet_orientation == MatePanelApplet.AppletOrient.LEFT:
|
|
45 |
return Gtk.PositionType.RIGHT
|
|
46 |
elif mate_applet_orientation == MatePanelApplet.AppletOrient.RIGHT:
|
|
47 |
return Gtk.PositionType.LEFT
|
|
48 |
|
|
49 |
class StatusWidget(Gtk.Button):
|
|
50 |
def __init__(self, icon, orientation, size):
|
|
51 |
super(Gtk.Button, self).__init__()
|
|
52 |
self.theme = Gtk.IconTheme.get_default()
|
|
53 |
self.orientation = orientation
|
|
54 |
self.size = size
|
|
55 |
|
|
56 |
self.get_style_context().add_class("statuswidget")
|
|
57 |
|
|
58 |
self.proxy = icon
|
|
59 |
|
|
60 |
# this is the bus owned name
|
|
61 |
self.name = self.proxy.get_name()
|
|
62 |
|
|
63 |
# this is (usually) the name of the remote process
|
|
64 |
self.proc_name = self.proxy.props.name
|
|
65 |
|
|
66 |
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
67 |
|
|
68 |
self.image = Gtk.Image(hexpand=True)
|
|
69 |
self.image.show()
|
|
70 |
self.label = Gtk.Label(no_show_all=True)
|
|
71 |
self.box.pack_start(self.image, False, False, 0)
|
|
72 |
self.box.pack_start(self.label, False, False, 0)
|
|
73 |
self.add(self.box)
|
|
74 |
|
|
75 |
flags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
|
|
76 |
|
|
77 |
self.proxy.bind_property("label", self.label, "label", flags)
|
|
78 |
self.proxy.bind_property("tooltip-text", self, "tooltip-text", flags)
|
|
79 |
self.proxy.bind_property("visible", self, "visible", flags)
|
|
80 |
|
|
81 |
self.proxy.connect("notify::icon-name", self._on_icon_name_changed)
|
|
82 |
|
|
83 |
self.in_widget = False
|
|
84 |
self.plain_surface = None
|
|
85 |
self.saturated_surface = None
|
|
86 |
|
|
87 |
self.connect("button-press-event", self.on_button_press)
|
|
88 |
self.connect("button-release-event", self.on_button_release)
|
|
89 |
self.connect("enter-notify-event", self.on_enter_notify)
|
|
90 |
self.connect("leave-notify-event", self.on_leave_notify)
|
|
91 |
|
|
92 |
self.update_orientation()
|
|
93 |
self.update_icon()
|
|
94 |
|
|
95 |
self.show_all()
|
|
96 |
|
|
97 |
def _on_icon_name_changed(self, proxy, gparamspec, data=None):
|
|
98 |
self.update_icon()
|
|
99 |
|
|
100 |
def update_icon(self):
|
|
101 |
string = self.proxy.props.icon_name
|
|
102 |
|
|
103 |
self.set_icon(string)
|
|
104 |
|
|
105 |
def update_orientation(self):
|
|
106 |
box_orientation = Gtk.Orientation.HORIZONTAL
|
|
107 |
|
|
108 |
if self.orientation in (MatePanelApplet.AppletOrient.LEFT, MatePanelApplet.AppletOrient.RIGHT):
|
|
109 |
box_orientation = Gtk.Orientation.VERTICAL
|
|
110 |
|
|
111 |
self.box.set_orientation(box_orientation)
|
|
112 |
|
|
113 |
if len(self.label.props.label) > 0 and box_orientation == Gtk.Orientation.HORIZONTAL:
|
|
114 |
self.label.set_visible(True)
|
|
115 |
self.label.set_margin_start(VISIBLE_LABEL_MARGIN)
|
|
116 |
else:
|
|
117 |
self.label.set_visible(False)
|
|
118 |
self.label.set_margin_start(0)
|
|
119 |
|
|
120 |
def set_icon(self, string):
|
|
121 |
size = self.size - ICON_SIZE_REDUCTION
|
|
122 |
|
|
123 |
# round down to even
|
|
124 |
if size % 2 != 0:
|
|
125 |
size -= 1
|
|
126 |
|
|
127 |
self.image.set_pixel_size(size)
|
|
128 |
|
|
129 |
try:
|
|
130 |
if os.path.exists(string):
|
|
131 |
icon_file = Gio.File.new_for_path(string)
|
|
132 |
icon = Gio.FileIcon.new(icon_file)
|
|
133 |
self.image.set_from_gicon(icon, Gtk.IconSize.MENU)
|
|
134 |
return
|
|
135 |
else:
|
|
136 |
if self.theme.has_icon(string):
|
|
137 |
icon = Gio.ThemedIcon.new(string)
|
|
138 |
self.image.set_from_gicon(icon, Gtk.IconSize.MENU)
|
|
139 |
return
|
|
140 |
except GLib.Error as e:
|
|
141 |
print("MateXAppStatusApplet: Could not load icon '%s' for '%s': %s" % (string, self.proc_name, e.message))
|
|
142 |
except TypeError as e:
|
|
143 |
print("MateXAppStatusApplet: Could not load icon '%s' for '%s': %s" % (string, self.proc_name, str(e)))
|
|
144 |
|
|
145 |
#fallback
|
|
146 |
self.image.set_from_icon_name("image-missing", Gtk.IconSize.MENU)
|
|
147 |
|
|
148 |
# TODO?
|
|
149 |
def on_enter_notify(self, widget, event):
|
|
150 |
self.in_widget = True
|
|
151 |
|
|
152 |
return Gdk.EVENT_PROPAGATE
|
|
153 |
|
|
154 |
def on_leave_notify(self, widget, event):
|
|
155 |
self.in_widget = False
|
|
156 |
|
|
157 |
return Gdk.EVENT_PROPAGATE
|
|
158 |
# /TODO
|
|
159 |
|
|
160 |
def on_button_press(self, widget, event):
|
|
161 |
orientation = translate_applet_orientation_to_xapp(self.orientation)
|
|
162 |
|
|
163 |
x, y = self.calc_menu_origin(widget, orientation)
|
|
164 |
self.proxy.call_button_press(x, y, event.button, event.time, orientation, None, None)
|
|
165 |
|
|
166 |
self.set_state_flags(Gtk.StateFlags.SELECTED, False)
|
|
167 |
|
|
168 |
if event.button == 3:
|
|
169 |
return Gdk.EVENT_STOP
|
|
170 |
|
|
171 |
return Gdk.EVENT_PROPAGATE
|
|
172 |
|
|
173 |
def on_button_release(self, widget, event):
|
|
174 |
orientation = translate_applet_orientation_to_xapp(self.orientation)
|
|
175 |
|
|
176 |
x, y = self.calc_menu_origin(widget, orientation)
|
|
177 |
self.proxy.call_button_release(x, y, event.button, event.time, orientation, None, None)
|
|
178 |
self.set_state_flags(Gtk.StateFlags.SELECTED, False)
|
|
179 |
|
|
180 |
if event.button == 3:
|
|
181 |
return Gdk.EVENT_STOP
|
|
182 |
|
|
183 |
return Gdk.EVENT_PROPAGATE
|
|
184 |
|
|
185 |
def calc_menu_origin(self, widget, orientation):
|
|
186 |
alloc = widget.get_allocation()
|
|
187 |
ignore, x, y = widget.get_window().get_origin()
|
|
188 |
rx = 0
|
|
189 |
ry = 0
|
|
190 |
|
|
191 |
if orientation == Gtk.PositionType.TOP:
|
|
192 |
rx = x + alloc.x
|
|
193 |
ry = y + alloc.y + alloc.height + INDICATOR_BOX_BORDER_COMP
|
|
194 |
elif orientation == Gtk.PositionType.BOTTOM:
|
|
195 |
rx = x + alloc.x
|
|
196 |
ry = y + alloc.y - INDICATOR_BOX_BORDER_COMP
|
|
197 |
elif orientation == Gtk.PositionType.LEFT:
|
|
198 |
rx = x + alloc.x + alloc.width + INDICATOR_BOX_BORDER_COMP
|
|
199 |
ry = y + alloc.y
|
|
200 |
elif orientation == Gtk.PositionType.RIGHT:
|
|
201 |
rx = x + alloc.x - INDICATOR_BOX_BORDER_COMP
|
|
202 |
ry = y + alloc.y
|
|
203 |
else:
|
|
204 |
rx = x
|
|
205 |
ry = y
|
|
206 |
|
|
207 |
return rx, ry
|
|
208 |
|
|
209 |
class MateXAppStatusApplet(object):
|
|
210 |
def __init__(self, applet, iid):
|
|
211 |
self.applet = applet
|
|
212 |
self.applet.set_flags(MatePanelApplet.AppletFlags.EXPAND_MINOR)
|
|
213 |
self.applet.set_can_focus(False)
|
|
214 |
self.applet.set_background_widget(self.applet)
|
|
215 |
|
|
216 |
button_css = Gtk.CssProvider()
|
|
217 |
|
|
218 |
if button_css.load_from_data(statusicon_css_string.encode()):
|
|
219 |
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), button_css, 600)
|
|
220 |
|
|
221 |
self.applet.connect("realize", self.on_applet_realized)
|
|
222 |
self.applet.connect("destroy", self.on_applet_destroy)
|
|
223 |
|
|
224 |
self.indicators = {}
|
|
225 |
self.monitor = None
|
|
226 |
|
|
227 |
def on_applet_realized(self, widget, data=None):
|
|
228 |
self.indicator_box = Gtk.Box(spacing=ICON_SPACING,
|
|
229 |
visible=True,
|
|
230 |
border_width=INDICATOR_BOX_BORDER)
|
|
231 |
|
|
232 |
self.applet.add(self.indicator_box)
|
|
233 |
self.applet.connect("change-size", self.on_applet_size_changed)
|
|
234 |
self.applet.connect("change-orient", self.on_applet_orientation_changed)
|
|
235 |
self.update_orientation()
|
|
236 |
|
|
237 |
if not self.monitor:
|
|
238 |
self.setup_monitor()
|
|
239 |
|
|
240 |
def on_applet_destroy(self, widget, data=None):
|
|
241 |
self.destroy_monitor()
|
|
242 |
Gtk.main_quit()
|
|
243 |
|
|
244 |
def setup_monitor (self):
|
|
245 |
self.monitor = XApp.StatusIconMonitor()
|
|
246 |
self.monitor.connect("icon-added", self.on_icon_added)
|
|
247 |
self.monitor.connect("icon-removed", self.on_icon_removed)
|
|
248 |
|
|
249 |
def destroy_monitor (self):
|
|
250 |
for key in self.indicators.keys():
|
|
251 |
self.indicator_box.remove(self.indicators[key])
|
|
252 |
|
|
253 |
self.monitor = None
|
|
254 |
self.indicators = {}
|
|
255 |
|
|
256 |
def on_icon_added(self, monitor, proxy):
|
|
257 |
name = proxy.get_name()
|
|
258 |
|
|
259 |
self.indicators[name] = StatusWidget(proxy, self.applet.get_orient(), self.applet.get_size())
|
|
260 |
self.indicator_box.add(self.indicators[name])
|
|
261 |
|
|
262 |
self.sort_icons()
|
|
263 |
|
|
264 |
def on_icon_removed(self, monitor, proxy):
|
|
265 |
name = proxy.get_name()
|
|
266 |
|
|
267 |
self.indicator_box.remove(self.indicators[name])
|
|
268 |
del(self.indicators[name])
|
|
269 |
|
|
270 |
self.sort_icons()
|
|
271 |
|
|
272 |
def update_orientation(self):
|
|
273 |
self.on_applet_orientation_changed(self, self.applet.get_orient())
|
|
274 |
|
|
275 |
def on_applet_size_changed(self, applet, size, data=None):
|
|
276 |
for key in self.indicators.keys():
|
|
277 |
indicator = self.indicators[key]
|
|
278 |
|
|
279 |
indicator.size = applet.get_size()
|
|
280 |
indicator.update_icon()
|
|
281 |
|
|
282 |
self.applet.queue_resize()
|
|
283 |
|
|
284 |
def on_applet_orientation_changed(self, applet, applet_orient, data=None):
|
|
285 |
orient = self.applet.get_orient()
|
|
286 |
|
|
287 |
for key in self.indicators.keys():
|
|
288 |
indicator = self.indicators[key]
|
|
289 |
|
|
290 |
indicator.orientation = orient
|
|
291 |
indicator.update_orientation()
|
|
292 |
|
|
293 |
box_orientation = Gtk.Orientation.HORIZONTAL
|
|
294 |
|
|
295 |
if orient in (MatePanelApplet.AppletOrient.LEFT, MatePanelApplet.AppletOrient.RIGHT):
|
|
296 |
box_orientation = Gtk.Orientation.VERTICAL
|
|
297 |
|
|
298 |
self.indicator_box.set_orientation(box_orientation)
|
|
299 |
self.indicator_box.queue_resize()
|
|
300 |
|
|
301 |
def sort_icons(self):
|
|
302 |
icon_list = list(self.indicators.values())
|
|
303 |
|
|
304 |
# for i in icon_list:
|
|
305 |
# print("before: ", i.proxy.props.icon_name, i.proxy.props.name.lower())
|
|
306 |
|
|
307 |
icon_list.sort(key=lambda icon: icon.proxy.props.name.lower())
|
|
308 |
icon_list.sort(key=lambda icon: icon.proxy.props.icon_name.lower().endswith("symbolic"))
|
|
309 |
|
|
310 |
# for i in icon_list:
|
|
311 |
# print("after: ", i.proxy.props.icon_name, i.proxy.props.name.lower())
|
|
312 |
|
|
313 |
icon_list.reverse()
|
|
314 |
|
|
315 |
for icon in icon_list:
|
|
316 |
self.indicator_box.reorder_child(icon, 0)
|
|
317 |
|
|
318 |
def applet_factory(applet, iid, data):
|
|
319 |
MateXAppStatusApplet(applet, iid)
|
|
320 |
applet.show()
|
|
321 |
return True
|
|
322 |
|
|
323 |
def quit_all(widget):
|
|
324 |
Gtk.main_quit()
|
|
325 |
sys.exit(0)
|
|
326 |
|
|
327 |
MatePanelApplet.Applet.factory_main("MateXAppStatusAppletFactory", True,
|
|
328 |
MatePanelApplet.Applet.__gtype__,
|
|
329 |
applet_factory, None)
|