#!/usr/bin/env python
 
import gtk, pygtk, gobject
import os, os.path, subprocess, sys
import netcfg
import ConfigParser
import pynotify
    
profile_dir="/etc/network.d"
state_dir="/var/run/network/profiles"
#helper_cmd="/home/iphitus/projects/netcfg-tray/netcfg-tray-helper"
helper_cmd="/usr/bin/netcfg-tray-helper"




class NetcfgTray (object):
    def __init__(self):    

        self.config = ConfigParser.SafeConfigParser()
        self.config.read("/etc/xdg/netcfg-tray/config")
        self.config.read(os.path.expanduser("~/.config/netcfg-tray/config"))
        self.load_config()

        self.statusIcon = gtk.StatusIcon()
        self.menu = gtk.Menu()
     
        self.statusIcon.set_tooltip("netcfg profile: rayner")
        self.statusIcon.connect('popup-menu', self.popup_menu)
        self.statusIcon.set_visible(True)
        self.update_status_icon(True)

        self.menu_sections=[self.menu_active, self.menu_inactive, self.menu_auto]
        
        
    def load_config(self):
        """Config file and set defaults if none available"""
        
        # Both user and 'defaults' are loaded. Quit if the defaults are broken by user or improperly installed
        try:
            n = self.config.get("notify","notify")
        except ConfigParser.NoSectionError:
            sys.exit(1)
  
        if n == "libnotify":
            self.notifications="libnotify"
            self.libnotify=pynotify.init("netcfg-tray")
        elif n == "external" and "external" in self.config.options("notify"):
            self.notifications="external"
        else:
            self.notifications=False

        try:
            self.root_cmd = self.config.get("main","root_cmd")
        except ConfigParser.NoSectionError:
            sys.exit(1)
        except ConfigParser.NoOptionError:
            sys.exit(1)


    def menu_active(self, menu):
        """Append active profile menu items to the passed menu"""
        
        item = gtk.MenuItem(label="Active profiles - click to disconnect")
        item.set_sensitive(False)
        menu.append(item)
        
        for prof in netcfg.get_active_profiles():
            item = gtk.MenuItem(label=prof.name+": "+prof["INTERFACE"]+" connected")
            item.connect('activate', self.profile_action, prof, "down")
            menu.append(item)
  
   
    def menu_auto(self, menu):
        """Append netcfg-auto-wireless entries to menu for *configured* wireless interfaces"""
        
        wifi_interfaces=set([ p["INTERFACE"] for p in netcfg.get_profiles() if p["CONNECTION"] == "wireless" ])
        for interface in wifi_interfaces:
            item = gtk.MenuItem(label="auto-wireless: "+ interface)
            item.connect('activate', self.profile_action, interface, "auto-wireless")
            menu.append(item)            
    
            
    def menu_inactive(self, menu):
        """Append all inactive profiles to the menu so that they may be connected"""
        
        item = gtk.MenuItem(label="Inactive profiles - click to connect")
        item.set_sensitive(False)
        menu.append(item)
           
        for prof in netcfg.get_profiles():
            if not prof in netcfg.get_active_profiles():
                item = gtk.MenuItem(label=prof.name)
                item.connect('activate', self.profile_action, prof, "up")
                menu.append(item)


    def populate_menu(self):    
        """Loop through all menu objects to populate main menu"""
        self.clear_menu()
            
        for section in self.menu_sections:
            # Each section is built by a separate command, run each passing the menu item
            section(self.menu)
            
            item = gtk.SeparatorMenuItem()
            self.menu.append(item)            
           
        item = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
        item.connect('activate', self.about)
        self.menu.append(item)
        
        item = gtk.ImageMenuItem(gtk.STOCK_QUIT)
        item.connect('activate', self.quit)
        self.menu.append(item)
 
 
    def clear_menu(self):
        """Clear the main menu, usually before it is updated and re-populated"""
        for item in self.menu.get_children():
            self.menu.remove(item)  
            item.destroy() # explicitly destroy so that ram usage doesnt increase with each menu popup      

    def profile_action(self, menuitem, arg, action):
        """Callback for various actions in the menu. TODO: Separate out different callback types"""

                   
        if action == "up": # arg is a profile
            self.notify("Connecting to "+arg.name)
            self.statusIcon.set_from_stock(gtk.STOCK_CONNECT)
            script=[self.root_cmd, helper_cmd, action, arg.name]
            
        elif action == "down": # arg is a profile
            self.notify("Disconnecting "+arg.name)
            self.statusIcon.set_from_stock(gtk.STOCK_DISCONNECT)  
            script=[self.root_cmd, helper_cmd, action, arg.name]
            
        elif action == "auto-wireless": # arg is an interface
            self.notify("Auto connecting "+arg)
            self.statusIcon.set_from_stock(gtk.STOCK_CONNECT)
            script=[self.root_cmd, helper_cmd, action, arg]     
                         
        # Run the specified network command...
        process = subprocess.Popen(script, stdout=subprocess.PIPE)
        # Call profile_action_completed when the process' stdout is closed. 
        gobject.io_add_watch(process.stdout, gobject.IO_HUP, self.profile_action_completed, process, arg, action)
         
         
    def profile_action_completed(self, stdout, state, process, profile, action):
        """Called when the started process' stdout is closed"""
        
        # Wait for process to actually finish, stdout may be closed before the process returns
        process.wait() 
        if action == "up":
            if process.returncode == 0:
                msg="Connected to "+profile.name
            else:
                msg="Connection to "+profile.name+" failed\n"+stdout.read()
        elif action == "down":
            if process.returncode == 0:
                msg="Disconnected "+profile.name
            else:
                msg="Disconnecting "+profile.name+" failed\n"+stdout.read()
        elif action == "auto-wireless":
            if process.returncode == 0:
                msg="Connected "+profile
            else:
                msg="Auto connection failed \n"+stdout.read()
        
        self.notify(msg)
        self.update_status_icon()
         
         
    def quit(self, widget=None, data = None):
        """Um. Quit. Most tray icons actually neglect this menu item, but its useful for development"""
        gtk.main_quit()
 
 
    def update_status_icon(self, repeat=False):
        """Update network profile status icon to reflect connectivity"""
        profiles = netcfg.get_active_profiles()
        # get the names out of the profiles
        names = [ n.name for n in profiles ]
        self.statusIcon.set_tooltip("Active profiles: "+",".join(names))
        if netcfg.get_active_profiles():
            self.statusIcon.set_from_stock(gtk.STOCK_NETWORK)
        else:
            self.statusIcon.set_from_stock(gtk.STOCK_DISCONNECT)
        if repeat:
            gobject.timeout_add(5000, self.update_status_icon, True)
         
            
    def popup_menu(self, widget, button, time):
        """Display right click menu"""
        
        if button == 3:
             self.populate_menu()
             self.menu.show_all()
             self.menu.popup(None, None, None, 3, time)
        pass


    def notify(self, message):
        """Output notification with whatever handler selected"""
        
        if self.notifications == "libnotify":
            pynotify.Notification("netcfg-tray",message).show()
        elif self.notifications == "external":
            subprocess.Popen(self.config.get("notify","external")+" "+message, stdout=subprocess.PIPE, shell=True)        
        

    def about(self, widget, data = None):
        """Obligatory about dialog. Awesome."""
        about = gtk.AboutDialog()
        about.set_name("netcfg-tray")
        about.set_version("v1")
        # TODO: Change to netcfg-tray 
        # Same license as netcfg, can assume existence as netcfg is obvious dep! For now...
        about.set_license(read("/usr/share/licenses/netcfg/LICENSE").read())
        about.set_website("http://archlinux.org")
        about.set_website_label("Arch Linux")
        about.run()
        about.destroy()

    def state_check(self):
        """Determine if interface is still connected"""
        active = netcfg.get_active_profiles()
        for profile in active:
            # Return codes are the reverse of True/False, hence a true here is actually a fail from grep.
            if subprocess.call("ip link show "+profile["INTERFACE"]+"|grep -q 'state UP'",shell=True):
                msg = profile.name + " appears to have been disconnected!"
                self.notify(msg)
        
                
if __name__ == '__main__':
    tray = NetcfgTray()
    gtk.main()
