- allow custom skin for setup as "GrowleeConfiguration", else fall back to new "Setup...
[vuplus_dvbapp-plugin] / growlee / src / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2
3 from Tools import Notifications
4 from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket, \
5                 GROWL_UDP_PORT, md5_constructor
6 from twisted.internet.protocol import DatagramProtocol
7 from twisted.internet import reactor
8 from struct import unpack
9 from socket import gaierror
10
11 from Screens.Setup import SetupSummary
12 from Screens.Screen import Screen
13 from Screens.MessageBox import MessageBox
14 from Components.ActionMap import ActionMap
15 from Components.config import config, getConfigListEntry, ConfigSubsection, \
16                 ConfigText, ConfigPassword, ConfigYesNo
17 from Components.ConfigList import ConfigListScreen
18 from Components.Sources.StaticText import StaticText
19
20 config.plugins.growlee = ConfigSubsection()
21 config.plugins.growlee.enable_incoming = ConfigYesNo(default=False)
22 config.plugins.growlee.enable_outgoing = ConfigYesNo(default=False)
23 config.plugins.growlee.address = ConfigText(fixed_size=False)
24 config.plugins.growlee.password = ConfigPassword()
25
26 class GrowleeConfiguration(Screen, ConfigListScreen):
27         def __init__(self, session):
28                 Screen.__init__(self, session)
29                 self.skinName = [ "GrowleeConfiguration", "Setup" ]
30
31                 # Buttons
32                 self["key_red"] = StaticText(_("Cancel"))
33                 self["key_green"] = StaticText(_("OK"))
34
35                 # Summary
36                 self.setup_title = "Growlee Configuration"
37                 self.onChangedEntry = []
38
39                 # Define Actions
40                 self["actions"] = ActionMap(["SetupActions"],
41                         {
42                                 "cancel": self.keyCancel,
43                                 "save": self.keySave,
44                         }
45                 )
46
47                 ConfigListScreen.__init__(
48                         self,
49                         [
50                                 getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
51                                 getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
52                                 getConfigListEntry(_("Address"), config.plugins.growlee.address),
53                                 getConfigListEntry(_("Password"), config.plugins.growlee.password),
54                         ],
55                         session=session,
56                         on_change=self.changed
57                 )
58
59                 # Trigger change
60                 self.changed()
61
62         def changed(self):
63                 for x in self.onChangedEntry:
64                         x()
65
66         def getCurrentEntry(self):
67                 return self["config"].getCurrent()[0]
68
69         def getCurrentValue(self):
70                 return str(self["config"].getCurrent()[1].getText())
71
72         def createSummary(self):
73                 return SetupSummary
74
75         def keySave(self):
76                 if self["config"].isChanged():
77                         global port
78                         if port:
79                                 def maybeConnect(*args, **kwargs):
80                                         if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
81                                                 global port
82                                                 port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
83
84                                 d = port.stopListening()
85                                 if d is not None:
86                                         d.addCallback(maybeConnect).addErrback(emergencyDisable)
87                                 else:
88                                         maybeConnect()
89                         elif config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
90                                 port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
91
92                 self.saveAll()
93                 self.close()
94
95 def configuration(session, **kwargs):
96         session.open(GrowleeConfiguration)
97
98 def emergencyDisable(*args, **kwargs):
99         global port
100         if port:
101                 port.stopListening()
102                 port = None
103
104         if gotNotification in Notifications.notificationAdded:
105                 Notifications.notificationAdded.remove(gotNotification)
106         Notifications.AddPopup(
107                 _("Network error.\nDisabling Growlee!"),
108                 MessageBox.TYPE_ERROR,
109                 10
110         )
111
112 class GrowlProtocolOneWrapper(DatagramProtocol):
113         def startProtocol(self):
114                 if config.plugins.growlee.enable_outgoing.value:
115                         addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
116                         p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
117                         p.addNotification()
118                         try:
119                                 self.transport.write(p.payload(), addr)
120                         except gaierror:
121                                 emergencyDisable()
122
123         def sendNotification(self, *args, **kwargs):
124                 if not self.transport or not config.plugins.growlee.enable_outgoing.value:
125                         return
126
127                 addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
128                 p = GrowlNotificationPacket(*args, **kwargs)
129                 try:
130                         self.transport.write(p.payload(), addr)
131                 except gaierror:
132                         emergencyDisable()
133
134         def datagramReceived(self, data, addr):
135                 Len = len(data)
136                 if Len < 16 or not config.plugins.growlee.enable_incoming.value:
137                         return
138
139                 digest = data[-16:]
140                 password = config.plugins.growlee.password.value
141                 checksum = md5_constructor()
142                 checksum.update(data[:-16])
143                 if password:
144                         checksum.update(password)
145                 if digest != checksum.digest():
146                         return
147
148                 # notify packet
149                 if data[1] == '\x01':
150                         nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
151                         notification, title, description = unpack(("%ds%ds%ds") % (nlen, tlen, dlen), data[12:Len-alen-16])
152
153                         # XXX: we should add a proper fix :-)
154                         Notifications.notificationAdded.remove(gotNotification)
155                         Notifications.AddPopup(
156                                 title + '\n' + description,
157                                 MessageBox.TYPE_INFO,
158                                 5
159                         )
160                         Notifications.notificationAdded.insert(0, gotNotification)
161
162                 # TODO: do we want to handle register packets? :-)
163
164 growlProtocolOneWrapper = GrowlProtocolOneWrapper()
165 port = None
166
167 def gotNotification():
168         notifications = Notifications.notifications
169         if notifications:
170                 _, screen, args, kwargs, _ = notifications[-1]
171                 if screen is MessageBox:
172
173                         if "text" in kwargs:
174                                 description = kwargs["text"]
175                         else:
176                                 description = args[0]
177                         description = description.decode('utf-8')
178
179                         growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value)
180
181 def autostart(**kwargs):
182         if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
183                 global port
184                 port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
185
186         # NOTE: we need to be the first one to be notified since other listeners
187         # may remove the notifications from the list for good
188         Notifications.notificationAdded.insert(0, gotNotification)
189
190 def Plugins(**kwargs):
191         return [
192                 PluginDescriptor(
193                         where=PluginDescriptor.WHERE_SESSIONSTART,
194                         fnc=autostart,
195                 ),
196                 PluginDescriptor(
197                         name="Growlee",
198                         description=_("Configure Growlee"), 
199                         where=PluginDescriptor.WHERE_PLUGINMENU,
200                         fnc=configuration,
201                 ),
202         ]
203