#!/usr/bin/python
# -*- coding: UTF-8 -*-
# -*- python -*-
# Copyright (C) 2005 by Sebastien Estienne
#
# This file may be distributed and/or modified under the terms of
# the GNU General Public License version 2 as published by
# the Free Software Foundation.
# This file is distributed without any warranty; without even the implied
# warranty of merchantability or fitness for a particular purpose.
# See "COPYING" in the source distribution for more information.
#
# $Id: service-discovery-applet.in 136 2007-03-19 11:53:01Z sebest $
#

import os
import subprocess
import sys
import pygtk

import pprint

import sdapplet.pluginloader
import sdapplet.pluginutils

pygtk.require('2.0')

def error_msg(msg):
    d = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
                          type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
    d.set_markup(msg)
    d.show_all()
    d.run()
    d.destroy()
    
try:
    import gettext
    gettext.bindtextdomain("service-discovery-applet", "/usr/share/locale")
    gettext.textdomain("service-discovery-applet")
    _ = gettext.gettext
    import gobject
    import avahi
    import dbus
    import gtk
    import gnomeapplet
    import gnome.ui
    import gconf
    import pynotify
    import avahi.ServiceTypeDatabase
except ImportError, e:
    error_msg(_("A required python module is missing!\n%s") % (e))
    sys.exit()

try:
    import dbus.glib
except ImportError, e:
    pass

###############################################################################
#
# ServiceTypeDatabase
#
class ServiceTypeDatabase:
    def __init__(self):
        self.pretty_name = avahi.ServiceTypeDatabase.ServiceTypeDatabase()

    def get_human_type(self, type):
	if self.pretty_name.has_key(type):
	    return self.pretty_name[type]
        else:
            return type

###############################################################################
#
# GCONF
#
class SDAGconf:
    def __init__(self, applet):
        self.applet = applet

	# Gconf Paths
	self.gc_options = "/apps/service-discovery-applet/options"
	self.gc_services =  "/apps/service-discovery-applet/services"

	# Gconf
	self.gc_client = gconf.client_get_default ()
    
	self.gc_client.add_dir (self.gc_options, gconf.CLIENT_PRELOAD_NONE)


	self.gc_client.add_dir (self.gc_services, gconf.CLIENT_PRELOAD_NONE)
	self.gc_client.notify_add (self.gc_services, self.gc_services_cb)
	self.gc_client.notify_add (self.gc_options, self.gc_options_cb)

        for (stype, desc) in avahi.ServiceTypeDatabase.ServiceTypeDatabase().items():
            if not self.gc_client.get("%s/%s" % (self.gc_services, stype)):
                enabled = self.applet.plugin.plugins.has_key(stype)
                self.gc_client.set_bool("%s/%s" % (self.gc_services, stype), enabled)

    def get_services(self):
        services = []
        gc_entries = self.gc_client.all_entries(self.gc_services)
	for gc_entry in gc_entries:
	    if self.gc_client.get_bool(gc_entry.key) == True:
		service_type = os.path.basename(gc_entry.key)
                services.append(service_type)
        return services
    
    def get_option(self, key):
        return self.gc_client.get_bool ("%s/%s" % (self.gc_options, key))
    
    # Callback called when a service is added/removed/enabled/disabled in gconf
    def gc_services_cb (self, client, cnxn_id, gc_entry, data):
	service_type = os.path.basename(gc_entry.key)
	if client.get_bool(gc_entry.key) == True:
	    # Browse for a new service
	    self.applet.add_service_type(self.applet.interface, self.applet.protocol, service_type, self.applet.domain)
	else:
	    # Stop browsing for a service
	    self.applet.del_service_type(self.applet.interface, self.applet.protocol, service_type, self.applet.domain)

    def gc_options_cb (self, client, cnxn_id, gc_entry, data):
	key = os.path.basename(gc_entry.key)
	if key == "show_notifications":
	    self.applet.show_notifications = client.get_bool(gc_entry.key)
	if key == "show_local_services":
	    self.applet.show_local_services = client.get_bool(gc_entry.key)
            self.applet.show_notifications = False
            status = self.applet.domain
            self.applet.stop_service_discovery(None,None,None)
            # only start if it was running before
            if len(status) != 0:
                self.applet.start_service_discovery(None,None,None)

###############################################################################
#
# SERVIDE DISCOVERY APPLET MAIN CLASS
#
class ServiceDiscoveryApplet(gnomeapplet.Applet):
    def __init__(self, applet, iid):
	self.__gobject_init__()
	self.applet = applet

	self.service_browsers = {}
	self.service_menu = gtk.Menu()
        self.service_menu.connect("hide", self.on_hide_service_menu)
        self.add_no_services_menuitem()
        
	self.zc_types = {}
	self.zc_services = {}

	# Plugins
	self.plugin = sdapplet.pluginloader.PluginLoader("/usr/share/service-discovery-applet/plugins/")

        self.sdaGconf = SDAGconf(self)
	self.show_local_services = self.sdaGconf.get_option("show_local_services")
	self.show_notifications = self.sdaGconf.get_option("show_notifications")

        applet.connect("button-press-event", self.on_button_press)
	applet.connect("size-allocate", self.on_applet_size_allocate)

	image = gtk.Image()
	image.set_from_file("/usr/share/service-discovery-applet/icons//24x24/service-discovery-applet.png")
	applet.add(image)

	# add tooltip to the applet
	tooltip = gtk.Tooltips()
	tooltip.set_tip(applet, _("Zeroconf Service Discovery"))
	tooltip.enable()
	
	applet.connect("change_background", self.on_change_background)
	applet.set_applet_flags(gnomeapplet.EXPAND_MINOR)

	# funky right-click menu
	menuXml = """
	<popup name="button3">
	<menuitem name="SDA About Item" verb="SDA About" _label="%s" pixtype="stock" pixname="gtk-about"/>
	<menuitem name="SDA Config Item" verb="SDA Config" _label="%s" pixtype="stock" pixname="gtk-preferences"/>
	</popup>
	"""
	menuXml = menuXml % (_("_About"), _("_Preferences"))

	applet.setup_menu(menuXml, [
		("SDA About", self.on_about),
		("SDA Config", self.on_config)
		], applet)
	self.popup = applet.get_popup_component()
        self.popup_control = applet.get_control()
    #Start Service Discovery
	self.domain = ""
        try:
            self.system_bus = dbus.SystemBus()
            self.system_bus.add_signal_receiver(self.avahi_dbus_connect_cb, "NameOwnerChanged", "org.freedesktop.DBus", arg0="org.freedesktop.Avahi")
        except dbus.DBusException, e:
            error_msg(_("<b>DBus Error:</b>\nCan't contact the system bus.\n\n Exiting..."))
            pprint.pprint(e)
            sys.exit(1)
            
        if not pynotify.init(_("Zeroconf Service Discovery")):
            error_msg(_("<b>Notification Daemon Error:</b>\n Notifications won't work."))
            self.applet.show_notifications = False
        
	self.start_service_discovery(None, None, None)
	
#	applet.connect("destroy",self.cleanup)
#	applet.show_all()

    def avahi_dbus_connect_cb(self, a, connect, disconnect):
        if connect != "":
            print "We are disconnected from avahi-daemon"
            self.stop_service_discovery(None, None, None)
        else:
            print "We are connected to avahi-daemon"
            self.start_service_discovery(None, None, None)

    def start_notifying_cb(self):
        print "start notifying"
	self.show_notifications = self.sdaGconf.get_option("show_notifications")


###############################################################################
#
# NOTIFICATIONS
#
    def display_service_notification(self, new, name, type):
	iconfile_path = "/usr/share/service-discovery-applet/icons//48x48/%s.png" % (type)
        print iconfile_path
	if not os.path.exists(iconfile_path):
	    iconfile = "file:///usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png"
        else:
            iconfile ="file://%s" % iconfile_path

        stdb = ServiceTypeDatabase()
        h_type = stdb.get_human_type(type)
	message = _("<b>Name :</b> %s\n<b>Type : </b> %s <i>(%s)</i>") % (name, h_type, type)

        if new == True:
            title = _("New service found")
        else:
            title = _("Service disappeared")

        self.display_notification(title, message, iconfile)

    def display_notification(self, title, message, iconfile = "file:///usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png"):
	try:
	    if self.show_notifications == True:
                n = pynotify.Notification(title, message, iconfile)
#                x,y = self.applet.window.get_origin()
#                n.set_hint("x",x)
#                n.set_hint("y",y)
#                n.attach_to_widget(self.applet.window)
                n.show()
	except:
	    print "can't use notification daemon"
            pass
 

###############################################################################
#
# AVAHI
#
    def siocgifname(self, interface):
	if interface <= 0:
	    return "any"
	else:
	    return self.server.GetNetworkInterfaceNameByIndex(interface)

    def service_resolved(self, interface, protocol, name, type, domain, host, aprotocol, address, port, txt, flags):
	print "Service data for service '%s' of type '%s' in domain '%s' on %s.%i:" % (name, type, domain, self.siocgifname(interface), protocol)
	print "\tHost %s (%s), port %i, TXT data: %s" % (host, address, port, avahi.txt_array_to_string_array(txt))

        txts = avahi.txt_array_to_string_array(txt)
        txts = sdapplet.pluginutils.pair_to_dict(txts)
        try:
            self.plugin.plugins[type][0].connect(self.use_host_names, name, type, host, address, port, txts)
        except KeyError,e:
            error_msg("No plugin to handle %s" % type)

    def print_error(self, err):
	# FIXME we should use notifications
	print "Error:", str(err)

    def menuitem_response(self, widget, interface, protocol, name, type, domain):
        self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_INET, dbus.UInt32(0), reply_handler=self.service_resolved, error_handler=self.print_error)

    def new_service(self, interface, protocol, name, type, domain, flags):
	print "Found service '%s' of type '%s' in domain '%s' on %s.%i." % (name, type, domain, self.siocgifname(interface), protocol)

        try:
            if self.show_local_services == False:
                if flags & avahi.LOOKUP_RESULT_LOCAL:
                    return
        except dbus.DBusException:
            pass

        # if we found a service, remove "No service found"
        if self.zc_types == {}:
            for menuitem in self.service_menu.get_children():
                self.service_menu.remove(menuitem)
        
        if self.zc_types.has_key(type) == False:
	    img = gtk.Image()
	    iconfile = "/usr/share/service-discovery-applet/icons//24x24/%s.png" % (type)
	    if not os.path.exists(iconfile):
		iconfile = "/usr/share/service-discovery-applet/icons//24x24/service-discovery-applet.png"
	    img.set_from_file(iconfile)

            stdb = ServiceTypeDatabase()
            h_type = stdb.get_human_type(type)
            menuitem =  gtk.ImageMenuItem(h_type)
	    menuitem.set_image(img)
	    menuitem.get_child().set_use_underline(False)
            
	    self.service_menu.add(menuitem)
	    self.zc_types[type] = gtk.Menu()
	    menuitem.set_submenu(self.zc_types[type])
	    menuitem.show_all()

	menuitem = gtk.MenuItem(name, False)
	self.zc_types[type].add(menuitem)
	self.zc_services[(interface, protocol, name, type, domain)] = menuitem
	menuitem.connect("activate", self.menuitem_response,interface, protocol, name, type, domain)
	menuitem.show_all()

        self.display_service_notification(True, name, type)

    def remove_service(self, interface, protocol, name, type, domain, flags):
	print "Service '%s' of type '%s' in domain '%s' on %s.%i disappeared." % (name, type, domain, self.siocgifname(interface), protocol)

        if self.zc_services.has_key((interface, protocol, name, type, domain)):
            self.zc_types[type].remove(self.zc_services[(interface, protocol, name, type, domain)])
	    self.display_service_notification(False, name, type)

	if self.zc_types.has_key(type) and self.zc_types[type].get_children() == []:
	    self.service_menu.remove(self.zc_types[type].get_attach_widget())
	    del self.zc_types[type]

	if self.zc_types == {}:
	    self.add_no_services_menuitem()

    def add_service_type(self, interface, protocol, type, domain):
	# Are we already browsing this domain for this type? 
	if self.service_browsers.has_key((interface, protocol, type, domain)):
	    return

	print "Browsing for services of type '%s' in domain '%s' on %s.%i ..." % (type, domain, self.siocgifname(interface), protocol)

        b = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, 
                                                      self.server.ServiceBrowserNew(interface, protocol, type, domain, dbus.UInt32(0)))
                           , avahi.DBUS_INTERFACE_SERVICE_BROWSER)
        b.connect_to_signal('ItemNew', self.new_service)
        b.connect_to_signal('ItemRemove', self.remove_service)
	
	self.service_browsers[(interface, protocol, type, domain)] = b

    def del_service_type(self, interface, protocol, type, domain):

	service = (interface, protocol, type, domain)
	if not self.service_browsers.has_key(service):
	    return
	sb = self.service_browsers[service]
        try:
            sb.Free()
        except dbus.DBusException:
            pass
	del self.service_browsers[service]
	# delete the sub menu of service_type
	if self.zc_types.has_key(type):
	    self.service_menu.remove(self.zc_types[type].get_attach_widget())
	    del self.zc_types[type]
        if len(self.zc_types) == 0:
            self.add_no_services_menuitem()

    def add_no_services_menuitem(self):
        for menuitem in self.service_menu.get_children():
            self.service_menu.remove(menuitem)
        menuitem = gtk.MenuItem(_("No services found"))
        menuitem.set_sensitive(False)
        self.service_menu.add(menuitem)
        menuitem.show_all()

###############################################################################
#
# APPLET CONTEXTUAL OPTIONS MENU
#
    def on_about(self, component, verb, applet):
	icon = gtk.Image()
	icon.set_from_file("/usr/share/service-discovery-applet/icons//48x48/service-discovery-applet.png")
	
	fullname = _("Zeroconf Service Discovery")
	copyright = _("Copyright (C) 2005 Sebastien Estienne")
	description = _("Shows Zeroconf Services on your local network and allows accessing them easily")
	authors = ["Sebastien Estienne <sebastien.estienne@gmail.com>", "Sebastian Dröge <slomo@ubuntu.com>"]
	translators = _("translator-credits")
	if translators == "translator-credits":
		translators = None
	
	about = gnome.ui.About(fullname, "0.4.5", copyright, description, authors, None, translators, icon.get_pixbuf())
	about.set_icon(icon.get_pixbuf())
	about.show()

    def on_config(self, component, verb, applet):
	pid = subprocess.Popen(["service-discovery-config", ""]).pid

    def start_service_discovery(self, component, verb, applet):
	if len(self.domain) != 0:
            print "domain not null %s" % (self.domain)
            self.display_notification(_("Already Discovering"),"")
	    return 
	try:
            self.server = dbus.Interface(self.system_bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), 
                                         avahi.DBUS_INTERFACE_SERVER)
            self.domain = self.server.GetDomainName()
	except:
            self.display_notification(_("Error Detected!"),_("Check that the Avahi daemon is running!"))
	    return

	try: 
		self.use_host_names = self.server.IsNSSSupportAvailable()
	except:
		self.use_host_names = False  

        self.display_notification(_("Starting discovery"),"")

	self.interface = avahi.IF_UNSPEC
	self.protocol = avahi.PROTO_INET

        services = self.sdaGconf.get_services()
        for service_type in services:
            self.add_service_type(self.interface, self.protocol, service_type, self.domain)
        
        # Wait one second before displaying notifications
        self.show_notifications = False
        gobject.timeout_add(1000, self.start_notifying_cb)

    def stop_service_discovery(self, component, verb, applet):
	if len(self.domain) == 0:
            self.display_notification(_("Discovery already stopped"),"")
	    return

	for service in self.service_browsers.copy():
	    self.del_service_type(service[0],service[1],service[2],service[3])
	self.domain = ""
        self.display_notification(_("Discovery stopped"),"")
    

###############################################################################
#
# APPLET CALLBACKS
#
    def position_popup_cb(self, widget):
        x, y = self.applet.window.get_origin()
        applet_width = self.applet.allocation.width
        applet_height = self.applet.allocation.height
        widget_width ,widget_height = widget.size_request()
        orientation = self.applet.get_orient()
        if orientation == gnomeapplet.ORIENT_UP:
            y -= widget_height
        elif orientation == gnomeapplet.ORIENT_DOWN:
            y += applet_height
        elif orientation == gnomeapplet.ORIENT_LEFT:
            x -= widget_width
        elif orientation == gnomeapplet.ORIENT_RIGHT:
            x += applet_width
        return (x, y, True)

    def on_button_press(self, widget, event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
            self.service_menu.show_all()
	    self.service_menu.popup(None, None, self.position_popup_cb, event.button, event.time)
            widget.set_state(gtk.STATE_SELECTED)
#        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
#            self.popup_control.do_popup(event.button,event.time)
	return False
        
    def on_hide_service_menu(self,widget):
        self.applet.set_state(gtk.STATE_NORMAL)
        return False

    def on_applet_size_allocate(self, eventbox, rect):
        if (rect.x <= 0) or (rect.y <= 0):
            return False
        rect.x -= 1
        rect.y -= 1
        rect.width  += 2
        rect.height += 2
        gtk.Widget.size_allocate(eventbox, rect)
        return False

    def on_change_background(self, panelapplet, backgroundtype, color, pixmap):
        panelapplet.modify_bg(gtk.STATE_NORMAL, color)
	if backgroundtype == gnomeapplet.PIXMAP_BACKGROUND:
            s = panelapplet.get_style()
	    s.bg_pixmap[gtk.STATE_NORMAL] = pixmap

###############################################################################
#
# STARTING POINT OF THE APPLET
#
def applet_factory(applet, iid):
    sda = ServiceDiscoveryApplet(applet, iid)
    sda.applet.show_all()
    return True

def activate_factory():
    gnomeapplet.bonobo_factory("OAFIID:GNOME_ServiceDiscoveryApplet_Factory", 
			       gnomeapplet.Applet.__gtype__, 
			       "Service discovery applet", "0", applet_factory)

def main():
    gobject.type_register(ServiceDiscoveryApplet)
    if len(sys.argv) == 2 and sys.argv[1] == "-window":
        applet_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        applet_window.set_title(_("Zeroconf Service Discovery"))
        applet_window.connect("destroy", gtk.main_quit)
        gnome.init("Service discovery applet", "0.4.5")
        applet = gnomeapplet.Applet()
        applet_factory(applet, None)
        applet.reparent(applet_window)
        applet_window.show_all()
        gtk.main()
    else:
        activate_factory()

if __name__ == "__main__":
    main()
