SHARE
TWEET

Untitled

a guest Mar 29th, 2010 400 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2.  
  3. #
  4. #   statnot - Status and Notifications
  5. #
  6. #   Lightweight notification-(to-become)-deamon intended to be used
  7. #   with lightweight WMs, like dwm.
  8. #   Receives Desktop Notifications (including libnotify / notify-send)
  9. #   See: http://www.galago-project.org/specs/notification/0.9/index.html
  10. #
  11. #   Note: VERY early prototype, to get feedback.
  12. #
  13. #   Copyright (c) 2009 by Henrik Hallberg (halhen@k2h.se)
  14. #   http://code.k2h.se
  15. #   Please report bugs or feature requests by e-mail.
  16. #
  17. #   This program is free software; you can redistribute it and/or modify
  18. #   it under the terms of the GNU General Public License as published by
  19. #   the Free Software Foundation; either version 2 of the License, or
  20. #   (at your option) any later version.
  21. #
  22. #   This program is distributed in the hope that it will be useful,
  23. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25. #   GNU General Public License for more details.
  26. #
  27. #   You should have received a copy of the GNU General Public License
  28. #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  29. #
  30.  
  31. import dbus
  32. import dbus.service
  33. import dbus.mainloop.glib
  34. import gobject
  35. import os
  36. import re
  37. import signal
  38. import subprocess
  39. import sys
  40. import thread
  41. import time
  42.  
  43. # ===== CONFIGURATION =====
  44.  
  45. # Default time a notification is show, unless specified in notification
  46. DEFAULT_NOTIFY_TIMEOUT = 3000 # milliseconds
  47.  
  48. # Maximum time a notification is allowed to show
  49. MAX_NOTIFY_TIMEOUT = 5000 # milliseconds
  50.  
  51. # Maximum number of characters in a notification.
  52. NOTIFICATION_MAX_LENGTH = 100 # number of characters
  53.  
  54. # Time between regular status updates
  55. STATUS_UPDATE_INTERVAL = 2.0 # seconds
  56.  
  57. # Command to fetch status text from. We read from stdout.
  58. # Each argument must be an element in the array
  59. #STATUS_COMMAND = ["/bin/sh", "%s/.stats/formatter.sh" % os.getenv("HOME")]
  60.  
  61. # Function run to format text
  62. def formatter(notification):
  63.     # Important stuff
  64.     color1 = "#888888"
  65.     color2 = "#1793D1"
  66.     highlight = lambda x: "^fg({0}){1}^fg({2})".format(color2, x, color1)
  67.  
  68.     if not notification: notification = ""
  69.     else: notification += "   "
  70.     artist, song = open("/home/kirby/.stats/cmus.status").read().splitlines()
  71.     song = highlight(song)
  72.     mail, updates = open("/home/kirby/.stats/pacmail.status").read().splitlines()
  73.     mail = "   E: {0}".format(highlight(mail)) if int(mail) else ""
  74.     updates = "   U: {0}".format(highlight(updates)) if int(updates) else ""
  75.     battery = subprocess.Popen(["acpi"], stdout=subprocess.PIPE).communicate()[0]
  76.     match = re.search(r"\d+%", battery)
  77.     battery = match.group() if match else ""
  78.     date = time.strftime(r"%a %d")
  79.     clock = highlight(time.strftime(r"%l.%M"))
  80.    
  81.     return "{0}   {1}: {2}   {3}  {4}{5}{6}   {7}".format(
  82.         battery, artist, song, date, clock, mail, updates, highlight(notification))
  83.  
  84. # update_text(text) is called when the status text should be updated
  85. # If there is a pending notification to be formatted, it is appended as
  86. # the final argument to the STATUS_COMMAND, e.g. as $1 in default shellscript
  87. # Uncomment the appropriate one below, or write your own.
  88.  
  89. # dummy (echo text)
  90. #def update_text(text):
  91. #    print text
  92.  
  93. # dwm
  94. #def update_text(text):
  95. #    subprocess.call(["xsetroot", "-name", text])
  96.  
  97. # i3
  98. dzen2 = subprocess.Popen(["dzen2", "-p",  "-x", "300", "-y", "890", "-ta", "r", "-h", "14"], stdin=subprocess.PIPE)
  99. def update_text(text):
  100.     dzen2.stdin.write(text+"\n")
  101.  
  102. # wmii
  103. #def update_text(text):
  104. #    subprocess.Popen(["wmiir", "write", "/rbar/status"], stdin=subprocess.PIPE).communicate(text)
  105.  
  106. def sighandler(signum, frame):
  107.     dzen2.terminate()
  108.     sys.exit()
  109.  
  110. signal.signal(signal.SIGTERM, sighandler)
  111.  
  112. # ===== CONFIGURATION END =====
  113.  
  114.  
  115.    
  116. # List of not shown notifications.
  117. # Array of arrays: [id, text, timeout in s]
  118. # 0th element is being displayed right now, and may change
  119. # Replacements of notification happens att add
  120. # message_thread only checks first element for changes
  121. notification_queue = []
  122. notification_queue_lock = thread.allocate_lock()
  123.  
  124. def add_notification(notif):
  125.     with notification_queue_lock:
  126.         for index, n in enumerate(notification_queue):
  127.             if n[0] == notif[0]: # same id, replace instead of queue
  128.                 n[1:] = notif[1:]
  129.                 return
  130.  
  131.         notification_queue.append(notif)
  132.  
  133. def next_notification(pop = False):
  134.     # No need to be thread safe here. Also most common scenario
  135.     if not notification_queue:
  136.         return None
  137.  
  138.     with notification_queue_lock:
  139.         # If there are several pending messages, discard the first 0-timeouts
  140.         while len(notification_queue) > 1 and notification_queue[0][2] == 0:
  141.             notification_queue.pop(0)
  142.  
  143.         if pop:
  144.             return notification_queue.pop(0)
  145.         else:
  146.             return notification_queue[0]
  147.  
  148. def get_statustext(notification = ''):
  149.     output = ''
  150.     try:
  151.         output = formatter(notification)
  152.     except:
  153.         sys.stderr.write("%s: could not read status message\n"
  154.                      % (sys.argv[0]))
  155.  
  156.     # Error - STATUS_COMMAND didn't exist or delivered empty result
  157.     # Fallback to notification only
  158.     if not output:
  159.         output = notification
  160.        
  161.     return output
  162.    
  163. def message_thread(dummy):
  164.     last_status_update = 0
  165.     last_notification_update = 0
  166.     current_notification_text = ''
  167.    
  168.     while 1:
  169.         notif = next_notification()
  170.         current_time = time.time()
  171.         update_status = False
  172.  
  173.         if notif:
  174.             if notif[1] != current_notification_text:
  175.                 update_status = True
  176.  
  177.             elif current_time > last_notification_update + notif[2]:
  178.                 # If requested timeout is zero, notification shows until
  179.                 # a new notification arrives or a regular status mesasge
  180.                 # cleans it
  181.                 # This way is a bit risky, but works. Keep an eye on this
  182.                 # when changing code
  183.                 if notif[2] != 0:
  184.                     update_status = True
  185.  
  186.                 # Pop expired notification
  187.                 next_notification(True)
  188.                 notif = next_notification()
  189.  
  190.             if update_status == True:
  191.                 last_notification_update = current_time
  192.                
  193.         if current_time > last_status_update + STATUS_UPDATE_INTERVAL:
  194.             update_status = True
  195.  
  196.         if update_status:
  197.             if notif:
  198.                 current_notification_text = notif[1]
  199.             else:
  200.                 current_notification_text = ''
  201.                
  202.             update_text(get_statustext(current_notification_text))
  203.             last_status_update = current_time
  204.                
  205.         time.sleep(0.1)        
  206.  
  207. class NotificationFetcher(dbus.service.Object):
  208.     _id = 0
  209.  
  210.     @dbus.service.method("org.freedesktop.Notifications",
  211.                          in_signature='susssasa{ss}i',
  212.                          out_signature='u')
  213.     def Notify(self, app_name, notification_id, app_icon,
  214.                summary, body, actions, hints, expire_timeout):
  215.         if (expire_timeout < 0) or (expire_timeout > MAX_NOTIFY_TIMEOUT):
  216.             expire_timeout = DEFAULT_NOTIFY_TIMEOUT
  217.  
  218.         if not notification_id:
  219.             self._id += 1
  220.             notification_id = self._id
  221.                        
  222.         text = ("%s %s" % (summary, body)).strip()
  223.         add_notification( [notification_id,
  224.                           text[:NOTIFICATION_MAX_LENGTH],
  225.                           int(expire_timeout) / 1000.0] )
  226.         return notification_id
  227.                
  228.     @dbus.service.method("org.freedesktop.Notifications", in_signature='', out_signature='as')
  229.     def GetCapabilities(self):
  230.         return ("body")
  231.        
  232.     @dbus.service.signal('org.freedesktop.Notifications', signature='uu')
  233.     def NotificationClosed(self, id_in, reason_in):
  234.         pass
  235.  
  236.     @dbus.service.method("org.freedesktop.Notifications", in_signature='u', out_signature='')
  237.     def CloseNotification(self, id):
  238.         pass
  239.  
  240. if __name__ == '__main__':
  241.     if len(sys.argv) > 1:
  242.         print "%s 0.0.2" % sys.argv[0]
  243.         sys.exit(1)
  244.  
  245.     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
  246.     session_bus = dbus.SessionBus()
  247.     name = dbus.service.BusName("org.freedesktop.Notifications", session_bus)
  248.     nf = NotificationFetcher(session_bus, '/org/freedesktop/Notifications')
  249.  
  250.     # We must use contexts and iterations to run threads
  251.     # http://www.jejik.com/articles/2007/01/python-gstreamer_threading_and_the_main_loop/
  252.     gobject.threads_init()
  253.     context = gobject.MainLoop().get_context()
  254.     thread.start_new_thread(message_thread, (None,))
  255.    
  256.     while 1:
  257.         context.iteration(True)
  258.     dzen2.terminate()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top