Codebase list xapp / f29b3d5
Add a Mate status applet (#72) * Add a status applet for mate-panel. * mate-xapp-status-applet.py: Some refining, cleanup * mate-xapp-status-applet: Fix rendering icons, sorting Michael Webster authored 3 years ago Clement Lefebvre committed 3 years ago
7 changed file(s) with 393 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
11 usr/bin/
22 usr/share/icons
33 usr/share/locale
4 usr/libexec/xapps
5 usr/share/mate-panel/applets
6 usr/share/dbus-1/services
4040 subdir('pygobject')
4141 subdir('files')
4242 subdir('schemas')
43 subdir('status-applets')
4344
4445 if get_option('docs')
4546 subdir('docs')
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)
0 ## Applet file
1
2 conf = configuration_data()
3 conf.set('locale', join_paths(get_option('prefix'), get_option('localedir')))
4
5 applet_file = configure_file(
6 input : 'mate-xapp-status-applet.py',
7 output: 'mate-xapp-status-applet.py',
8 configuration: conf,
9 )
10
11 install_data(applet_file,
12 install_dir: join_paths(get_option('libexecdir'), 'xapps')
13 )
14
15 ## DBus service file
16
17 conf = configuration_data()
18 conf.set('libexec', join_paths(get_option('prefix'), get_option('libexecdir')))
19
20 service_file = configure_file(
21 input : 'org.mate.panel.applet.MateXAppStatusAppletFactory.service.in',
22 output: 'org.mate.panel.applet.MateXAppStatusAppletFactory.service',
23 configuration: conf,
24 )
25
26 install_data(service_file,
27 install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services')
28 )
29
30 ## Applet definition file
31
32 def_file = configure_file(
33 input : 'org.x.MateXAppStatusApplet.mate-panel-applet.in',
34 output: 'org.x.MateXAppStatusApplet.mate-panel-applet',
35 configuration: conf,
36 )
37
38 install_data(def_file,
39 install_dir: join_paths(get_option('datadir'), 'mate-panel', 'applets')
40 )
41
0 [D-BUS Service]
1 Name=org.mate.panel.applet.MateXAppStatusAppletFactory
2 Exec=@libexec@/xapps/mate-xapp-status-applet.py
0 [Applet Factory]
1 Id=MateXAppStatusAppletFactory
2 InProcess=false
3 Location=@libexec@/xapps/mate-xapp-status-applet.py
4 Name=XAppStatusApplet Factory
5 Description=DBus-based status icon panel applet
6
7 [MateXAppStatusApplet]
8 Name=Mate XApp Status Applet
9 Description=DBus-based status icon panel applet
10 Icon=panel-applets
11 MateComponentId=OAFIID:MATE_MateXAppStatusApplet;
12