PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/GrowlNetwork.py

https://github.com/xiongchiamiov/mumbles
Python | 239 lines | 152 code | 47 blank | 40 comment | 22 complexity | 04790466540a66b6197e8b7980af8f46 MD5 | raw file
Possible License(s): GPL-2.0
  1. #!/usr/bin/env python
  2. # Simple Growl Network Utilities
  3. # Steve Benz (steve.benz@gmail.com)
  4. # dot_j (dot_j@mumbles-project.org)
  5. # Original Idea, GrowlPacked by Rui Carmo (http://the.taoofmac.com)
  6. # (C) 2006 Rui Carmo. Code under BSD License.
  7. # Some of the request handler code is from technovelty.com
  8. # http://www.technovelty.org/code/python/socketserver.html
  9. import SocketServer, time, select, sys, struct
  10. from threading import Thread
  11. import os
  12. import dbus
  13. import dbus.service
  14. try:
  15. import hashlib
  16. except ImportError:
  17. import md5
  18. GROWL_UDP_PORT=9887
  19. GROWL_PROTOCOL_VERSION=1
  20. GROWL_TYPE_REGISTRATION=0
  21. GROWL_TYPE_NOTIFICATION=1
  22. GROWL_DBUS_NAME = 'info.growl.Growl'
  23. GROWL_DBUS_OBJECT = '/info/growl/Growl'
  24. GROWL_DBUS_INTERFACE = 'info.growl.Growl'
  25. class GrowlDBus(dbus.service.Object):
  26. def __init__(self,bus_name):
  27. dbus.service.Object.__init__(self,bus_name,GROWL_DBUS_OBJECT)
  28. @dbus.service.signal(dbus_interface=GROWL_DBUS_INTERFACE, signature='ss')
  29. def Notify(self, title, message):
  30. pass
  31. class GrowlPacket:
  32. """Performs basic decoding of a Growl UDP Packet."""
  33. def __init__(self, data, password = None):
  34. """Initializes and validates the packet"""
  35. self.valid = False
  36. self.data = data
  37. self.digest = self.data[-16:]
  38. try:
  39. checksum = hashlib.md5()
  40. except:
  41. checksum = md5.new()
  42. checksum.update(self.data[:-16])
  43. if password:
  44. checksum.update(password)
  45. if self.digest == checksum.digest():
  46. self.valid = True
  47. # end def
  48. def type(self):
  49. """Returns the packet type"""
  50. if self.data[1] == '\x01':
  51. return 'NOTIFY'
  52. else:
  53. return 'REGISTER'
  54. # end def
  55. def info(self):
  56. """Returns a subset of packet information"""
  57. if self.type() == 'NOTIFY':
  58. nlen = struct.unpack("!H",str(self.data[4:6]))[0]
  59. tlen = struct.unpack("!H",str(self.data[6:8]))[0]
  60. dlen = struct.unpack("!H",str(self.data[8:10]))[0]
  61. alen = struct.unpack("!H",str(self.data[10:12]))[0]
  62. return struct.unpack(("%ds%ds%ds%ds") % (nlen, tlen, dlen, alen), self.data[12:len(self.data)-16])
  63. else:
  64. length = struct.unpack("!H",str(self.data[2:4]))[0]
  65. return self.data[6:7+length]
  66. # end def
  67. # end class
  68. class GrowlNotificationPacket:
  69. """Builds a Growl Network Notification packet.
  70. Defaults to emulating the command-line growlnotify utility."""
  71. def __init__(self, application="growlnotify",
  72. notification="Command-Line Growl Notification", title="Title",
  73. description="Description", priority = 0, sticky = False, password = None ):
  74. self.application = application.encode("utf-8")
  75. self.notification = notification.encode("utf-8")
  76. self.title = title.encode("utf-8")
  77. self.description = description.encode("utf-8")
  78. flags = (priority & 0x07) * 2
  79. if priority < 0:
  80. flags |= 0x08
  81. if sticky:
  82. flags = flags | 0x0001
  83. self.data = struct.pack( "!BBHHHHH",
  84. GROWL_PROTOCOL_VERSION,
  85. GROWL_TYPE_NOTIFICATION,
  86. flags,
  87. len(self.notification),
  88. len(self.title),
  89. len(self.description),
  90. len(self.application) )
  91. self.data += self.notification
  92. self.data += self.title
  93. self.data += self.description
  94. self.data += self.application
  95. try:
  96. self.checksum = hashlib.md5()
  97. except:
  98. self.checksum = md5.new()
  99. self.checksum.update(self.data)
  100. if password:
  101. self.checksum.update(password)
  102. self.data += self.checksum.digest()
  103. # end def
  104. def payload(self):
  105. """Returns the packet payload."""
  106. return self.data
  107. # end def
  108. # end class
  109. class GrowlRegistrationPacket:
  110. """Builds a Growl Network Registration packet.
  111. Defaults to emulating the command-line growlnotify utility."""
  112. def __init__(self, application="growlnotify", password = None ):
  113. self.notifications = []
  114. self.defaults = [] # array of indexes into notifications
  115. self.application = application.encode("utf-8")
  116. self.password = password
  117. # end def
  118. def addNotification(self, notification="Command-Line Growl Notification", enabled=True):
  119. """Adds a notification type and sets whether it is enabled on the GUI"""
  120. self.notifications.append(notification)
  121. if enabled:
  122. self.defaults.append(len(self.notifications)-1)
  123. # end def
  124. def payload(self):
  125. """Returns the packet payload."""
  126. self.data = struct.pack( "!BBH",
  127. GROWL_PROTOCOL_VERSION,
  128. GROWL_TYPE_REGISTRATION,
  129. len(self.application) )
  130. self.data += struct.pack( "BB",
  131. len(self.notifications),
  132. len(self.defaults) )
  133. self.data += self.application
  134. for notification in self.notifications:
  135. encoded = notification.encode("utf-8")
  136. self.data += struct.pack("!H", len(encoded))
  137. self.data += encoded
  138. for default in self.defaults:
  139. self.data += struct.pack("B", default)
  140. try:
  141. self.checksum = hashlib.md5()
  142. except:
  143. self.checksum = md5.new()
  144. self.checksum.update(self.data)
  145. if self.password:
  146. self.checksum.update(self.password)
  147. self.data += self.checksum.digest()
  148. return self.data
  149. # end def
  150. # end class
  151. # SimpleServer extends the UDPServer, using the threading mix in
  152. # to create a new thread for every request.
  153. class GrowlServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
  154. # This means the main server will not do the equivalent of a
  155. # pthread_join() on the new threads. With this set, Ctrl-C will
  156. # kill the server reliably.
  157. daemon_threads = True
  158. # By setting this we allow the server to re-bind to the address by
  159. # setting SO_REUSEADDR, meaning you don't have to wait for
  160. # timeouts when you kill the server and the sockets don't get
  161. # closed down correctly.
  162. allow_reuse_address = True
  163. active = False
  164. def __init__(self, server_address, RequestHandlerClass, password=None):
  165. self.password = password
  166. SocketServer.UDPServer.__init__(self, server_address, RequestHandlerClass)
  167. try:
  168. self.__bus = dbus.SessionBus()
  169. except:
  170. print "Error: DBus appears to not be running."
  171. return False
  172. dbus_object = self.__bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
  173. dbus_iface = dbus.Interface(dbus_object, "org.freedesktop.DBus")
  174. name = dbus.service.BusName(GROWL_DBUS_NAME,bus=self.__bus)
  175. self.dbus = GrowlDBus(name)
  176. def update(self, active, password):
  177. self.active = active
  178. self.password = password
  179. # The RequestHandler handles an incoming request.
  180. class growlIncoming(SocketServer.DatagramRequestHandler):
  181. def __init__(self, request, client_address, server):
  182. SocketServer.DatagramRequestHandler.__init__(self, request, client_address, server)
  183. def handle(self):
  184. if self.server.active:
  185. p = GrowlPacket(self.rfile.read(), self.server.password)
  186. if p.valid:
  187. if p.type() == 'NOTIFY':
  188. notification,title,description,app = p.info()
  189. mpath = os.path.dirname(__file__)
  190. self.server.dbus.Notify(title, description)
  191. def finish(self):
  192. pass