#!/usr/bin/env python
# vim: set fileencoding=utf-8
#
#   sinod - Simple Notifications Daemon
#
#   Based on statnot by Henrik Hallberg (halhen@k2h.se) http://code.k2h.se
#
#   Lightweight notification-deamon intended to be used with scripts in
#   lightweight WMs, like dwm or with non-x environments.
#
#   Receives Desktop Notifications (including libnotify / notify-send)
#   See: http://www.galago-project.org/specs/notification/0.9/index.html
#
#   Note: VERY early prototype, to get feedback.
#
#   Copyright (c) 2009 by Henrik Hallberg (halhen@k2h.se)
#   Copyright (c) 2009 by Paweł Zuzelski (pawelz@pld-linux.org)
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import dbus
import dbus.service
import dbus.mainloop.glib
import gobject
import sys
import thread
import time

# ===== CONFIGURATION =====

# Default time a notification is show, unless specified in notification
DEFAULT_NOTIFY_TIMEOUT = 3000 # milliseconds

# Maximum time a notification is allowed to show
MAX_NOTIFY_TIMEOUT = 5000 # milliseconds

# Maximum number of characters in a notification. 
NOTIFICATION_MAX_LENGTH = 100 # number of characters

def update_text(text):
    if len(text):
        print text

# ===== CONFIGURATION END =====


    
# List of not shown notifications.
# Array of arrays: [id, text, timeout in s]
# 0th element is being displayed right now, and may change
# Replacements of notification happens att add
# message_thread only checks first element for changes
notification_queue = []
notification_queue_lock = thread.allocate_lock()

def add_notification(notif):
    with notification_queue_lock:
        for index, n in enumerate(notification_queue):
            if n[0] == notif[0]: # same id, replace instead of queue
                n[1:] = notif[1:]
                return

        notification_queue.append(notif)

def next_notification():
    # No need to be thread safe here. Also most common scenario
    if not notification_queue:
        return None

    with notification_queue_lock:
        # If there are several pending messages, discard the first 0-timeouts
        while len(notification_queue) > 1 and notification_queue[0][2] == 0:
            notification_queue.pop(0)

        return notification_queue.pop(0)

def message_thread(dummy):
    last_notification = 0
    last_notification_update = 0
    current_notification_text = ''
    
    while 1:
        notif = next_notification()
        current_time = time.time()
        do_notify = False

        if notif:
            if notif[1] != current_notification_text:
                do_notify = True

        if do_notify:
            if notif:
                current_notification_text = notif[1]
            else:
                current_notification_text = ''
                
            update_text(current_notification_text)
            last_notification = current_time
                
        time.sleep(1)		

class NotificationFetcher(dbus.service.Object):
    _id = 0

    @dbus.service.method("org.freedesktop.Notifications",
                         in_signature='susssasa{ss}i',
                         out_signature='u')
    def Notify(self, app_name, notification_id, app_icon,
               summary, body, actions, hints, expire_timeout):
        if (expire_timeout < 0) or (expire_timeout > MAX_NOTIFY_TIMEOUT):
            expire_timeout = DEFAULT_NOTIFY_TIMEOUT

        if not notification_id:
            self._id += 1
            notification_id = self._id
			
        text = ("%s %s" % (summary, body)).strip()
        add_notification( [notification_id,
                          text[:NOTIFICATION_MAX_LENGTH],
                          int(expire_timeout) / 1000.0] )
        return notification_id
		
    @dbus.service.method("org.freedesktop.Notifications", in_signature='', out_signature='as')
    def GetCapabilities(self):
        return ("body")
	
    @dbus.service.signal('org.freedesktop.Notifications', signature='uu')
    def NotificationClosed(self, id_in, reason_in):
        pass

    @dbus.service.method("org.freedesktop.Notifications", in_signature='u', out_signature='')
    def CloseNotification(self, id):
        pass

if __name__ == '__main__':
    if len(sys.argv) > 1:
        print "%s CURVERSION" % sys.argv[0]
        sys.exit(1)

    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    session_bus = dbus.SessionBus()
    name = dbus.service.BusName("org.freedesktop.Notifications", session_bus)
    nf = NotificationFetcher(session_bus, '/org/freedesktop/Notifications')

    # We must use contexts and iterations to run threads
    # http://www.jejik.com/articles/2007/01/python-gstreamer_threading_and_the_main_loop/
    gobject.threads_init()
    context = gobject.MainLoop().get_context()
    thread.start_new_thread(message_thread, (None,))
    
    try:
        while 1:
            context.iteration(True)
    except:
        exit(0)

