1 | #␊ |
2 | # This program is free software: you can redistribute it and/or modify␊ |
3 | # it under the terms of the GNU General Public License as published by␊ |
4 | # the Free Software Foundation, either version 3 of the License, or␊ |
5 | # (at your option) any later version.␊ |
6 | #␊ |
7 | # This program is distributed in the hope that it will be useful,␊ |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of␊ |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the␊ |
10 | # GNU General Public License for more details.␊ |
11 | #␊ |
12 | # You should have received a copy of the GNU General Public License␊ |
13 | # along with this program. If not, see <https://www.gnu.org/licenses/>.␊ |
14 | #␊ |
15 | ␊ |
16 | import json␊ |
17 | import dbus␊ |
18 | from dbus.mainloop.glib import DBusGMainLoop␊ |
19 | import gi␊ |
20 | gi.require_version('Gst', '1.0')␊ |
21 | from gi.repository import GObject, GLib, Gst␊ |
22 | ␊ |
23 | #␊ |
24 | # DBUS interface␊ |
25 | #␊ |
26 | class GenericMonitor:␊ |
27 | """␊ |
28 | Class that manage DBUS communication with GNOME generic monitor addon.␊ |
29 | You have to subclass it␊ |
30 | """␊ |
31 | ␊ |
32 | def setupMonitor(self):␊ |
33 | """ Setup DBUS stuff (equivalent to constructor) """ ␊ |
34 | self._activated = True␊ |
35 | self._encoder = json.JSONEncoder()␊ |
36 | self._dbus_interface = 'com.soutade.GenericMonitor'␊ |
37 | ␊ |
38 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)␊ |
39 | self._dbus = dbus.SessionBus()␊ |
40 | ␊ |
41 | self.systray_proxy = self._dbus.get_object('org.gnome.Shell', '/com/soutade/GenericMonitor')␊ |
42 | ␊ |
43 | self.add_signal_receiver(self.onClick, 'onClick')␊ |
44 | self.add_signal_receiver(self.onDblClick, 'onDblClick')␊ |
45 | self.add_signal_receiver(self.onRightClick, 'onRightClick')␊ |
46 | self.add_signal_receiver(self.onRightDblClick, 'onRightDblClick')␊ |
47 | self.add_signal_receiver(self.onScrollUp, 'onScrollUp')␊ |
48 | self.add_signal_receiver(self.onScrollDown, 'onScrollDown')␊ |
49 | self.add_signal_receiver(self.onEnter, 'onEnter')␊ |
50 | self.add_signal_receiver(self.onLeave, 'onLeave')␊ |
51 | ␊ |
52 | self.add_signal_receiver(self.onActivate, 'onActivate')␊ |
53 | self.add_signal_receiver(self.onDeactivate, 'onDeactivate')␊ |
54 | ␊ |
55 | def runMainLoop(self):␊ |
56 | """ Start infinite loop that allows to send and receive events and functions """ ␊ |
57 | self._mainLoop = GLib.MainLoop()␊ |
58 | self._mainLoop.run()␊ |
59 | ␊ |
60 | def stopMainLoop(self):␊ |
61 | """ Stop infinite main loop """␊ |
62 | self._mainLoop.quit()␊ |
63 | ␊ |
64 | # Generic Monitor functions␊ |
65 | def notify(self, group):␊ |
66 | """ Send notify() function ␊ |
67 | Parameters␊ |
68 | ----------␊ |
69 | group : GenericMonitorGroup␊ |
70 | group to notify␊ |
71 | """␊ |
72 | if self._activated:␊ |
73 | if type(group) == GenericMonitorGroup:␊ |
74 | group = group.getValues()␊ |
75 | self.systray_proxy.notify(self._encoder.encode(group), dbus_interface=self._dbus_interface)␊ |
76 | ␊ |
77 | def deleteItems(self, items):␊ |
78 | """ Send deleteItems() function ␊ |
79 | Parameters␊ |
80 | ----------␊ |
81 | items : list of str (<itemName>@<groupName>)␊ |
82 | items to delete␊ |
83 | """␊ |
84 | if self._activated:␊ |
85 | items = {'items':items}␊ |
86 | self.systray_proxy.deleteItems(self._encoder.encode(items), dbus_interface=self._dbus_interface)␊ |
87 | ␊ |
88 | def deleteGroups(self, groups):␊ |
89 | """ Send deleteGroups() function ␊ |
90 | Parameters␊ |
91 | ----------␊ |
92 | groups : list of str (<groupName>)␊ |
93 | groups to delete␊ |
94 | """␊ |
95 | if self._activated:␊ |
96 | groups = {'groups':groups}␊ |
97 | self.systray_proxy.deleteGroups(self._encoder.encode(groups), dbus_interface=self._dbus_interface)␊ |
98 | ␊ |
99 | def openPopup(self, item):␊ |
100 | """ Send openPopup() function ␊ |
101 | Parameters␊ |
102 | ----------␊ |
103 | item : str (<itemName>@<groupName>)␊ |
104 | Open popup (if there is one) of item␊ |
105 | """␊ |
106 | if self._activated:␊ |
107 | item = {'item':item}␊ |
108 | self.systray_proxy.openPopup(self._encoder.encode(item), dbus_interface=self._dbus_interface)␊ |
109 | ␊ |
110 | def closePopup(self, item):␊ |
111 | """ Send closePopup() function ␊ |
112 | Parameters␊ |
113 | ----------␊ |
114 | item : str (<itemName>@<groupName>)␊ |
115 | Close popup (if there is one) of item␊ |
116 | """␊ |
117 | if self._activated:␊ |
118 | item = {'item':item}␊ |
119 | self.systray_proxy.closePopup(self._encoder.encode(item), dbus_interface=self._dbus_interface)␊ |
120 | ␊ |
121 | def togglePopup(self, item):␊ |
122 | """ Send togglePopup() function ␊ |
123 | Parameters␊ |
124 | ----------␊ |
125 | item : str (<itemName>@<groupName>)␊ |
126 | Toggle popup (if there is one) of item␊ |
127 | """␊ |
128 | if self._activated:␊ |
129 | item = {'item':item}␊ |
130 | self.systray_proxy.togglePopup(self._encoder.encode(item), dbus_interface=self._dbus_interface)␊ |
131 | ␊ |
132 | # Generic Monitor signals␊ |
133 | def onClick(self, sender):␊ |
134 | """ onClick event ␊ |
135 | Parameters␊ |
136 | ----------␊ |
137 | sender : str (<itemName>@<groupName>)␊ |
138 | Sender which event has been raised␊ |
139 | """␊ |
140 | pass␊ |
141 | ␊ |
142 | def onRightClick(self, sender):␊ |
143 | """ onRightClick event ␊ |
144 | Parameters␊ |
145 | ----------␊ |
146 | sender : str (<itemName>@<groupName>)␊ |
147 | Sender which event has been raised␊ |
148 | """␊ |
149 | pass␊ |
150 | ␊ |
151 | def onDblClick(self, sender):␊ |
152 | """ onDblClick event ␊ |
153 | Parameters␊ |
154 | ----------␊ |
155 | sender : str (<itemName>@<groupName>)␊ |
156 | Sender which event has been raised␊ |
157 | """␊ |
158 | pass␊ |
159 | ␊ |
160 | def onRightDblClick(self, sender):␊ |
161 | """ onRightDblClick event ␊ |
162 | Parameters␊ |
163 | ----------␊ |
164 | sender : str (<itemName>@<groupName>)␊ |
165 | Sender which event has been raised␊ |
166 | """␊ |
167 | pass␊ |
168 | ␊ |
169 | def onEnter(self, sender):␊ |
170 | """ onEnter event (mouse enter in item)␊ |
171 | Parameters␊ |
172 | ----------␊ |
173 | sender : str (<itemName>@<groupName>)␊ |
174 | Sender which event has been raised␊ |
175 | """␊ |
176 | pass␊ |
177 | ␊ |
178 | def onLeave(self, sender):␊ |
179 | """ onLeave event (mouse leave item)␊ |
180 | Parameters␊ |
181 | ----------␊ |
182 | sender : str (<itemName>@<groupName>)␊ |
183 | Sender which event has been raised␊ |
184 | """␊ |
185 | pass␊ |
186 | ␊ |
187 | def onScrollUp(self, sender):␊ |
188 | """ onScrollUp event ␊ |
189 | Parameters␊ |
190 | ----------␊ |
191 | sender : str (<itemName>@<groupName>)␊ |
192 | Sender which event has been raised␊ |
193 | """␊ |
194 | pass␊ |
195 | ␊ |
196 | def onScrollDown(self, sender):␊ |
197 | """ onScrollDown event ␊ |
198 | Parameters␊ |
199 | ----------␊ |
200 | sender : str (<itemName>@<groupName>)␊ |
201 | Sender which event has been raised␊ |
202 | """␊ |
203 | pass␊ |
204 | ␊ |
205 | def onActivate(self):␊ |
206 | """ onActivate event (addon activated)␊ |
207 | """␊ |
208 | self._activated = True␊ |
209 | ␊ |
210 | def onDeactivate(self):␊ |
211 | """ onDeactivate event (addon deactivated)␊ |
212 | """␊ |
213 | self._activated = False␊ |
214 | ␊ |
215 | # DBUS method␊ |
216 | """ Add callback when DBUS signal is raised␊ |
217 | Parameters␊ |
218 | ----------␊ |
219 | callback : function␊ |
220 | Callback raised on DBUS signal␊ |
221 | signalName : str␊ |
222 | Name of DBUS signal␊ |
223 | interface : str␊ |
224 | Name of DBUS interface␊ |
225 | """␊ |
226 | def add_signal_receiver(self, callback, signalName, interface=None):␊ |
227 | if not interface:␊ |
228 | interface = self._dbus_interface␊ |
229 | self._dbus.add_signal_receiver(callback, signalName, interface)␊ |
230 | ␊ |
231 | ␊ |
232 | #␊ |
233 | # Item stuff␊ |
234 | #␊ |
235 | class GenericMonitorGenericWidget:␊ |
236 | """ Generic widget class, parent of all widgets␊ |
237 | """␊ |
238 | def __init__(self, style='', name='', signals={}):␊ |
239 | """␊ |
240 | Parameters␊ |
241 | ----------␊ |
242 | name : str, optional␊ |
243 | Widget name␊ |
244 | signals : dictionary, optional␊ |
245 | Dictionary of signals and their action␊ |
246 | """␊ |
247 | self.valuesToMap = ['name', 'style']␊ |
248 | self.mapValues = {}␊ |
249 | self.mapName = ''␊ |
250 | self.style = style␊ |
251 | self.name = name␊ |
252 | self.signals = signals␊ |
253 | ␊ |
254 | def setStyle(self, style):␊ |
255 | self.style = style␊ |
256 | ␊ |
257 | def _toMap(self):␊ |
258 | """ Return dictionary of class elements to send to addon␊ |
259 | """␊ |
260 | self.mapValues = {}␊ |
261 | for p in self.valuesToMap:␊ |
262 | if self.__dict__[p]:␊ |
263 | self.mapValues[p] = self.__dict__[p]␊ |
264 | for (name, value) in self.signals.items():␊ |
265 | self.mapValues[name] = value␊ |
266 | return {self.mapName:self.mapValues}␊ |
267 | ␊ |
268 | class GenericMonitorTextWidget(GenericMonitorGenericWidget):␊ |
269 | """ Text widget␊ |
270 | """␊ |
271 | def __init__(self, text, style='', name='', signals={}):␊ |
272 | """␊ |
273 | Parameters␊ |
274 | ----------␊ |
275 | text : str␊ |
276 | Text to display␊ |
277 | style : str, optional␊ |
278 | CSS style␊ |
279 | name : str, optional␊ |
280 | Widget name␊ |
281 | signals : dictionary, optional␊ |
282 | Dictionary of signals and their action␊ |
283 | """␊ |
284 | super().__init__(style, name, signals)␊ |
285 | self.valuesToMap += ['text']␊ |
286 | self.mapName = 'text'␊ |
287 | ␊ |
288 | self.text = text␊ |
289 | ␊ |
290 | def setText(self, text):␊ |
291 | self.text = text␊ |
292 | ␊ |
293 | class GenericMonitorIconWidget(GenericMonitorGenericWidget):␊ |
294 | """ Icon widget␊ |
295 | """␊ |
296 | def __init__(self, path, style=''):␊ |
297 | """␊ |
298 | Parameters␊ |
299 | ----------␊ |
300 | path : str␊ |
301 | Icon path␊ |
302 | style : str, optional␊ |
303 | CSS style␊ |
304 | """␊ |
305 | super().__init__(style=style)␊ |
306 | self.valuesToMap += ['path']␊ |
307 | self.mapName = 'icon'␊ |
308 | ␊ |
309 | self.path = path␊ |
310 | self.style = style␊ |
311 | ␊ |
312 | def setPath(self, path):␊ |
313 | self.path = path␊ |
314 | ␊ |
315 | ␊ |
316 | class GenericMonitorPictureWidget(GenericMonitorGenericWidget):␊ |
317 | """ Picture widget␊ |
318 | """␊ |
319 | def __init__(self, path, style='', width=-1, height=-1, name='', signals={}):␊ |
320 | """␊ |
321 | Parameters␊ |
322 | ----------␊ |
323 | path : str␊ |
324 | Picture path␊ |
325 | style : str, optional␊ |
326 | CSS style␊ |
327 | width : int, optional␊ |
328 | Width of displayed picture (-1 for default width)␊ |
329 | height : int, optional␊ |
330 | Width of displayed picture (-1 for default width)␊ |
331 | name : str, optional␊ |
332 | Widget name␊ |
333 | signals : dictionary, optional␊ |
334 | Dictionary of signals and their action␊ |
335 | """␊ |
336 | ␊ |
337 | super().__init__(style, name, signals)␊ |
338 | self.valuesToMap += ['path', 'width', 'height']␊ |
339 | self.mapName = 'picture'␊ |
340 | self.path = path␊ |
341 | ␊ |
342 | self.width = width␊ |
343 | self.height = height␊ |
344 | ␊ |
345 | def setPath(self, path):␊ |
346 | self.path = path␊ |
347 | ␊ |
348 | def setWidth(self, width):␊ |
349 | self.width = width␊ |
350 | ␊ |
351 | def setHeight(self, height):␊ |
352 | self.height = height␊ |
353 | ␊ |
354 | class GenericMonitorPopup(GenericMonitorGenericWidget):␊ |
355 | """ Popup of current item␊ |
356 | """␊ |
357 | def __init__(self, items):␊ |
358 | """␊ |
359 | Parameters␊ |
360 | ----------␊ |
361 | items : list of GenericMonitorTextWidget and GenericMonitorPictureWidget␊ |
362 | List of items (text or picture)␊ |
363 | """␊ |
364 | self.valuesToMap = ('items',)␊ |
365 | self.mapName = 'popup'␊ |
366 | ␊ |
367 | self.items = items␊ |
368 | ␊ |
369 | def _toMap(self):␊ |
370 | self.mapValues = {}␊ |
371 | self.mapValues['items'] = []␊ |
372 | for item in self.items:␊ |
373 | self.mapValues['items'] += [item._toMap()]␊ |
374 | return {self.mapName:self.mapValues}␊ |
375 | ␊ |
376 | def clear(self):␊ |
377 | """ Clear items list␊ |
378 | """␊ |
379 | self.items = []␊ |
380 | ␊ |
381 | def setItems(self, items):␊ |
382 | self.items = items␊ |
383 | ␊ |
384 | class GenericMonitorItem:␊ |
385 | """ Addon item that will be displayed in status bar␊ |
386 | """␊ |
387 | def __init__(self, name, items=[], signals={}, popup=None, box='center'):␊ |
388 | """␊ |
389 | Parameters␊ |
390 | ----------␊ |
391 | name : str␊ |
392 | Item name␊ |
393 | items : list of GenericMonitorTextWidget and GenericMonitorIconWidget, optional␊ |
394 | List of items (text or icon)␊ |
395 | signals : dictionary, optional␊ |
396 | Dictionary of signals and their action␊ |
397 | "on-click" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
398 | "on-dblclick" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
399 | "on-rightclick" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
400 | "on-rightdblclick" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
401 | "on-click" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
402 | "on-enter" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
403 | "on-leave" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
404 | "on-scroll" : ["signal"|"delete"|"open-popup"|"close-popup"|"toggle-popup"]␊ |
405 | popup : GenericMonitorPopup, optional␊ |
406 | Popup to be displayed␊ |
407 | box : str, optional␊ |
408 | Box were to put items : left, center (default), or right␊ |
409 | """␊ |
410 | self.name = name␊ |
411 | self.items = items␊ |
412 | self.signals = signals␊ |
413 | self.popup = popup␊ |
414 | self.box = box␊ |
415 | self.group = ''␊ |
416 | ␊ |
417 | self._checkValues()␊ |
418 | ␊ |
419 | def _checkValues(self):␊ |
420 | if not self.name:␊ |
421 | raise ValueError('Need a name')␊ |
422 | if len(self.items) > 2:␊ |
423 | raise ValueError('Maximum 2 items can be displayed')␊ |
424 | for (name, value) in self.signals.items():␊ |
425 | if not name in ('on-click', 'on-dblclick', 'on-rightclick', 'on-rightdblclick',␊ |
426 | 'on-enter', 'on-leave', 'on-scroll'): ␊ |
427 | raise ValueError('Invalid signal name ' + name)␊ |
428 | if not value in ('signal', 'delete', 'open-popup', 'close-popup', 'toggle-popup'):␊ |
429 | raise ValueError('Invalid signal value ' + value)␊ |
430 | for item in self.items:␊ |
431 | if not isinstance(item, GenericMonitorGenericWidget):␊ |
432 | raise ValueError('Invalid item ' + item)␊ |
433 | if self.popup and not isinstance(self.popup, GenericMonitorPopup):␊ |
434 | raise ValueError('Invalid popup object')␊ |
435 | if self.box and not self.box in ('left', 'center', 'right'):␊ |
436 | raise ValueError('Invalid box value')␊ |
437 | ␊ |
438 | def setGroup(self, group):␊ |
439 | """ Set current group (automatically done when added in a group)␊ |
440 | Parameters␊ |
441 | ----------␊ |
442 | group : str␊ |
443 | Group name␊ |
444 | """␊ |
445 | self.group = group␊ |
446 | ␊ |
447 | def getName(self):␊ |
448 | return self.name␊ |
449 | ␊ |
450 | def getFullName(self):␊ |
451 | """ return full name used by addon␊ |
452 | """␊ |
453 | return '%s@%s' % (self.name, self.group)␊ |
454 | ␊ |
455 | def _toMap(self):␊ |
456 | myMap = {}␊ |
457 | for p in ('name', 'box'):␊ |
458 | if self.__dict__[p]:␊ |
459 | myMap[p] = self.__dict__[p]␊ |
460 | for item in self.items:␊ |
461 | item._toMap()␊ |
462 | myMap[item.mapName] = item.mapValues␊ |
463 | if self.popup:␊ |
464 | self.popup._toMap()␊ |
465 | myMap['popup'] = self.popup.mapValues␊ |
466 | for (name, value) in self.signals.items():␊ |
467 | myMap[name] = value␊ |
468 | return [myMap]␊ |
469 | ␊ |
470 | class GenericMonitorGroup:␊ |
471 | """ Group of items␊ |
472 | """␊ |
473 | def __init__(self, name, items=[]):␊ |
474 | """␊ |
475 | Parameters␊ |
476 | ----------␊ |
477 | name : str␊ |
478 | Group name␊ |
479 | items : list of GenericMonitorItem, optional␊ |
480 | List of items␊ |
481 | """␊ |
482 | self.name = name␊ |
483 | self.items = []␊ |
484 | if type(items) != list:␊ |
485 | self.addItem(items)␊ |
486 | else:␊ |
487 | self.addItems(items)␊ |
488 | ␊ |
489 | def addItem(self, item):␊ |
490 | """ Add item into the groupw␊ |
491 | Parameters␊ |
492 | ----------␊ |
493 | item : GenericMonitorItem␊ |
494 | Item to add␊ |
495 | """␊ |
496 | item.setGroup(self.name)␊ |
497 | self.items.append(item)␊ |
498 | ␊ |
499 | def addItems(self, items):␊ |
500 | """ Add items into the group␊ |
501 | Parameters␊ |
502 | ----------␊ |
503 | items : list of GenericMonitorItem␊ |
504 | Items to add␊ |
505 | """␊ |
506 | for item in items:␊ |
507 | self.addItem(item)␊ |
508 | ␊ |
509 | def clear(self):␊ |
510 | """ Clear items list␊ |
511 | """␊ |
512 | for item in items:␊ |
513 | item.setGroup('')␊ |
514 | self.items = []␊ |
515 | ␊ |
516 | def getValues(self):␊ |
517 | """ Returns group and its items in addon format␊ |
518 | """␊ |
519 | res = {'group': self.name, 'items':[]}␊ |
520 | for item in self.items:␊ |
521 | res['items'] += item._toMap()␊ |
522 | return res␊ |
523 | ␊ |
524 | def __str__(self):␊ |
525 | return str(self.getValues())␊ |