initial checkin of growlee, a plugin that sends growl notifications of messagebox...
authorMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Sat, 3 Oct 2009 18:40:23 +0000 (18:40 +0000)
committerMoritz Venn <ritzmo@users.schwerkraft.elitedvb.net>
Sat, 3 Oct 2009 18:40:23 +0000 (18:40 +0000)
Makefile.am
configure.ac
growlee/CONTROL/control [new file with mode: 0644]
growlee/Makefile.am [new file with mode: 0644]
growlee/src/LICENSE [new file with mode: 0644]
growlee/src/Makefile.am [new file with mode: 0644]
growlee/src/__init__.py [new file with mode: 0644]
growlee/src/maintainer.info [new file with mode: 0644]
growlee/src/netgrowl.py [new file with mode: 0644]
growlee/src/plugin.py [new file with mode: 0644]

index 34bc764..262b510 100644 (file)
@@ -13,8 +13,9 @@ SUBDIRS = \
        filebrowser \
        fritzcall \
        ftpbrowser \
-       googlemaps \
        genuinedreambox \
+       googlemaps \
+       growlee \
        httpproxy \
        imdb \
        kiddytimer \
index ae1752e..6b7449b 100644 (file)
@@ -245,4 +245,7 @@ ftpbrowser/Makefile
 ftpbrowser/po/Makefile
 ftpbrowser/src/Makefile
 ftpbrowser/src/images/Makefile
+
+growlee/Makefile
+growlee/src/Makefile
 ])
diff --git a/growlee/CONTROL/control b/growlee/CONTROL/control
new file mode 100644 (file)
index 0000000..4615d6e
--- /dev/null
@@ -0,0 +1,10 @@
+Package: enigma2-plugin-extensions-growlee
+Version: 0.1-20091001-r0
+Description: redirect notifications to growl
+Architecture: mipsel
+Section: extra
+Priority: optional
+Maintainer: Moritz Venn <moritz.venn@freaque.net>
+Homepage: http://www.ritzmo.de
+Depends: enigma2(>=2.6git20090615), twisted
+Source: http://enigma2-plugins.schwerkraft.elitedvb.net/
diff --git a/growlee/Makefile.am b/growlee/Makefile.am
new file mode 100644 (file)
index 0000000..308a09c
--- /dev/null
@@ -0,0 +1 @@
+SUBDIRS = src\r
diff --git a/growlee/src/LICENSE b/growlee/src/LICENSE
new file mode 100644 (file)
index 0000000..614d30b
--- /dev/null
@@ -0,0 +1,12 @@
+All Files of this Software are licensed under the Creative Commons 
+Attribution-NonCommercial-ShareAlike 3.0 Unported 
+License if not stated otherwise in a Files Head. To view a copy of this license, visit
+http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
+Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+
+Alternatively, this plugin may be distributed and executed on hardware which
+is licensed by Dream Multimedia GmbH.
+
+This plugin is NOT free software. It is open source, you are allowed to
+modify it (if you keep the license), but it may not be commercially 
+distributed other than under the conditions noted above.
diff --git a/growlee/src/Makefile.am b/growlee/src/Makefile.am
new file mode 100644 (file)
index 0000000..38cd702
--- /dev/null
@@ -0,0 +1,4 @@
+installdir = /usr/lib/enigma2/python/Plugins/Extensions/Growlee\r
+\r
+install_PYTHON = *.py\r
+install_DATA = maintainer.info LICENSE\r
diff --git a/growlee/src/__init__.py b/growlee/src/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/growlee/src/maintainer.info b/growlee/src/maintainer.info
new file mode 100644 (file)
index 0000000..452813f
--- /dev/null
@@ -0,0 +1,2 @@
+moritz.venn@freaque.net
+Growlee
diff --git a/growlee/src/netgrowl.py b/growlee/src/netgrowl.py
new file mode 100644 (file)
index 0000000..6e2d2e1
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+
+"""Growl 0.6 Network Protocol Client for Python"""
+__version__ = "0.6.2"
+__author__ = "Rui Carmo (http://the.taoofmac.com)"
+__copyright__ = "(C) 2004 Rui Carmo. Code under BSD License."
+__contributors__ = "Ingmar J Stein (Growl Team), John Morrissey (hashlib patch)"
+
+try:
+  import hashlib
+  md5_constructor = hashlib.md5
+except ImportError:
+  import md5
+  md5_constructor = md5.new
+
+import struct
+from socket import AF_INET, SOCK_DGRAM, socket
+
+GROWL_UDP_PORT=9887
+GROWL_PROTOCOL_VERSION=1
+GROWL_TYPE_REGISTRATION=0
+GROWL_TYPE_NOTIFICATION=1
+
+class GrowlRegistrationPacket:
+  """Builds a Growl Network Registration packet.
+     Defaults to emulating the command-line growlnotify utility."""
+
+  def __init__(self, application="growlnotify", password = None ):
+    self.notifications = []
+    self.defaults = [] # array of indexes into notifications
+    self.application = application.encode("utf-8")
+    self.password = password
+  # end def
+
+
+  def addNotification(self, notification="Command-Line Growl Notification", enabled=True):
+    """Adds a notification type and sets whether it is enabled on the GUI"""
+    self.notifications.append(notification)
+    if enabled:
+      self.defaults.append(len(self.notifications)-1)
+  # end def
+
+
+  def payload(self):
+    """Returns the packet payload."""
+    self.data = struct.pack( "!BBH",
+                             GROWL_PROTOCOL_VERSION,
+                             GROWL_TYPE_REGISTRATION,
+                             len(self.application) )
+    self.data += struct.pack( "BB",
+                              len(self.notifications),
+                              len(self.defaults) )
+    self.data += self.application
+    for notification in self.notifications:
+      encoded = notification.encode("utf-8")
+      self.data += struct.pack("!H", len(encoded))
+      self.data += encoded
+    for default in self.defaults:
+      self.data += struct.pack("B", default)
+    self.checksum = md5_constructor()
+    self.checksum.update(self.data)
+    if self.password:
+       self.checksum.update(self.password)
+    self.data += self.checksum.digest()
+    return self.data
+  # end def
+# end class
+
+
+class GrowlNotificationPacket:
+  """Builds a Growl Network Notification packet.
+     Defaults to emulating the command-line growlnotify utility."""
+
+  def __init__(self, application="growlnotify",
+               notification="Command-Line Growl Notification", title="Title",
+               description="Description", priority = 0, sticky = False, password = None ):
+    self.application  = application.encode("utf-8")
+    self.notification = notification.encode("utf-8")
+    self.title        = title.encode("utf-8")
+    self.description  = description.encode("utf-8")
+    flags = (priority & 0x07) * 2
+    if priority < 0:
+      flags |= 0x08
+    if sticky:
+      flags = flags | 0x0100
+    self.data = struct.pack( "!BBHHHHH",
+                             GROWL_PROTOCOL_VERSION,
+                             GROWL_TYPE_NOTIFICATION,
+                             flags,
+                             len(self.notification),
+                             len(self.title),
+                             len(self.description),
+                             len(self.application) )
+    self.data += self.notification
+    self.data += self.title
+    self.data += self.description
+    self.data += self.application
+    self.checksum = md5_constructor()
+    self.checksum.update(self.data)
+    if password:
+       self.checksum.update(password)
+    self.data += self.checksum.digest()
+  # end def
+
+  def payload(self):
+    """Returns the packet payload."""
+    return self.data
+  # end def
+# end class
+
+
+if __name__ == '__main__':
+  print "Starting Unit Test"
+  print " - please make sure Growl is listening for network notifications"
+  addr = ("localhost", GROWL_UDP_PORT)
+  s = socket(AF_INET,SOCK_DGRAM)
+  print "Assembling registration packet like growlnotify's (no password)"
+  p = GrowlRegistrationPacket()
+  p.addNotification()
+  print "Sending registration packet"
+  s.sendto(p.payload(), addr)
+
+  print "Assembling standard notification packet"
+  p = GrowlNotificationPacket()
+  print "Sending standard notification packet"
+  s.sendto(p.payload(), addr)
+
+  s.close()
+"""
+  print "Assembling priority -2 (Very Low) notification packet"
+  p = GrowlNotificationPacket(priority=-2)
+  print "Sending priority -2 notification packet"
+  s.sendto(p.payload(), addr)
+
+  print "Assembling priority 2 (Very High) sticky notification packet"
+  p = GrowlNotificationPacket(priority=2,sticky=True)
+  print "Sending priority 2 (Very High) sticky notification packet"
+  s.sendto(p.payload(), addr)
+  s.close()
+  print "Done."
+"""
diff --git a/growlee/src/plugin.py b/growlee/src/plugin.py
new file mode 100644 (file)
index 0000000..8c35d13
--- /dev/null
@@ -0,0 +1,173 @@
+from Plugins.Plugin import PluginDescriptor
+
+from Tools import Notifications
+from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket, \
+               GROWL_UDP_PORT, md5_constructor
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+from struct import unpack
+
+from Screens.Setup import SetupSummary
+from Screens.Screen import Screen
+from Screens.MessageBox import MessageBox
+from Components.ActionMap import ActionMap
+from Components.config import config, getConfigListEntry, ConfigSubsection, \
+               ConfigText, ConfigPassword, ConfigYesNo
+from Components.ConfigList import ConfigListScreen
+
+config.plugins.growlee = ConfigSubsection()
+config.plugins.growlee.enable_incoming = ConfigYesNo(default=False)
+config.plugins.growlee.enable_outgoing = ConfigYesNo(default=False)
+config.plugins.growlee.address = ConfigText(fixed_size=False)
+config.plugins.growlee.password = ConfigPassword()
+
+class GrowleeConfiguration(Screen, ConfigListScreen):
+       skin = """<screen title="Growlee Configuration" position="75,155" size="565,280">
+               <widget name="config" position="5,5" size="555,100" scrollbarMode="showOnDemand" />
+       </screen>"""
+
+       def __init__(self, session):
+               Screen.__init__(self, session)
+
+               # Summary
+               self.setup_title = "Growlee Configuration"
+               self.onChangedEntry = []
+
+               # Define Actions
+               self["actions"] = ActionMap(["SetupActions"],
+                       {
+                               "cancel": self.keySave,
+                       }
+               )
+
+               ConfigListScreen.__init__(
+                       self,
+                       [
+                               getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
+                               getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
+                               getConfigListEntry(_("Address"), config.plugins.growlee.address),
+                               getConfigListEntry(_("Password"), config.plugins.growlee.password),
+                       ],
+                       session=session,
+                       on_change=self.changed
+               )
+
+               # Trigger change
+               self.changed()
+
+       def changed(self):
+               for x in self.onChangedEntry:
+                       try:
+                               x()
+                       except:
+                               pass
+
+       def getCurrentEntry(self):
+               return self["config"].getCurrent()[0]
+
+       def getCurrentValue(self):
+               return str(self["config"].getCurrent()[1].getText())
+
+       def createSummary(self):
+               return SetupSummary
+
+       def keySave(self):
+               if self["config"].isChanged():
+                       global port
+                       if port:
+                               port.stopListening()
+
+                       if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
+                               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+
+               self.saveAll()
+               self.close()
+
+def configuration(session, **kwargs):
+       session.open(GrowleeConfiguration)
+
+class GrowlProtocolOneWrapper(DatagramProtocol):
+       def startProtocol(self):
+               if config.plugins.growlee.enable_outgoing.value:
+                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
+                       p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
+                       p.addNotification()
+                       self.transport.write(p.payload(), addr)
+
+       def sendNotification(self, *args, **kwargs):
+               if not self.transport or not config.plugins.growlee.enable_outgoing.value:
+                       return
+
+               addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
+               p = GrowlNotificationPacket(*args, **kwargs)
+               self.transport.write(p.payload(), addr)
+
+       def datagramReceived(self, data, addr):
+               Len = len(data)
+               if Len < 16 or not config.plugins.growlee.enable_incoming.value:
+                       return
+
+               digest = data[-16:]
+               password = config.plugins.growlee.password.value
+               checksum = md5_constructor()
+               checksum.update(data[:-16])
+               if password:
+                       checksum.update(password)
+               if digest != checksum.digest():
+                       return
+
+               # notify packet
+               if data[1] == '\x01':
+                       nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
+                       notification, title, description = unpack(("%ds%ds%ds") % (nlen, tlen, dlen), data[12:Len-alen-16])
+
+                       # XXX: we should add a proper fix :-)
+                       Notifications.notificationAdded.remove(gotNotification)
+                       Notifications.AddPopup(
+                               title + '\n' + description,
+                               MessageBox.TYPE_INFO,
+                               5
+                       )
+                       Notifications.notificationAdded.insert(0, gotNotification)
+
+               # TODO: do we want to handle register packets? :-)
+
+growlProtocolOneWrapper = GrowlProtocolOneWrapper()
+port = None
+
+def gotNotification():
+       notifications = Notifications.notifications
+       if notifications:
+               _, screen, args, kwargs, _ = notifications[-1]
+               if screen is MessageBox:
+
+                       if "text" in kwargs:
+                               description = kwargs["text"]
+                       else:
+                               description = args[0]
+
+                       growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value)
+
+def autostart(**kwargs):
+       if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
+               global port
+               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
+
+       # NOTE: we need to be the first one to be notified since other listeners
+       # may remove the notifications from the list for good
+       Notifications.notificationAdded.insert(0, gotNotification)
+
+def Plugins(**kwargs):
+       return [
+               PluginDescriptor(
+                       where=PluginDescriptor.WHERE_SESSIONSTART,
+                       fnc=autostart,
+               ),
+               PluginDescriptor(
+                       name="Growlee",
+                       description=_("Configure Growlee"), 
+                       where=PluginDescriptor.WHERE_PLUGINMENU,
+                       fnc=configuration,
+               ),
+       ]
+