#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os
import sys
import socket
import struct
import fcntl
import string
import gtk
import gobject
import gtk.glade
try:
  import gnomecanvas
except:
  import gnome.canvas as gnomecanvas
from gtk import RcStyle
from time import localtime, strftime

# make the path below a link to the theme you want
# example: sudo ln -s /usr/share/ldm/themes/Ubuntu /usr/share/ldm/themes/default
themedir = "/usr/share/ldm/themes/default"

def get_ip(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])

class GTKGreeter:
    def run(self):
        self.ok = False
        self.flag = 0
    
        # set default gtk theme
        try:
            gtk.rc_parse ('/usr/lib/ltsp/greeters/gtkrc')
        except:
            gtk.rc_parse ('/usr/share/themes/Clearlooks/gtk-2.0/gtkrc')
      
        # default settings for lang and session
        self.language = ["Default", "None"]
        self.session = ["Default", "None", "NOIP"]
        self.lang = 'None'
        self.sess = 'None'

        # get glade widget tree
        try:
            self.wTree = gtk.glade.XML('/usr/lib/ltsp/greeters/greeter.glade')
        except:
            self.wTree = gtk.glade.XML('greeter.glade')
    
        # care for look and feel
        self.set_cursor()
        self.theme_engine()
        self.draw_login_window()
    
        # configurable options
        self.draw_buttons() # session and lang, should be wrapped by a config option
        self.draw_bottom_bar(False) # set to True if you want a clock
        self.draw_shutdown_button()
    
        self.win.show_all()
    
        # the entry field has focus by default
        self.entry.grab_focus()
    
        gtk.main()
    
        if self.ok == True:
            print self.username
            print self.password
            print self.lang
            print self.sess
            return True
        return False

    def draw_login_window(self):
        # get the username/password entry from the glade tree
        self.entry = self.wTree.get_widget("entry")
        self.entry.unparent()
        self.entry.connect("activate", lambda w: self.switch_entry(self.flag))
     
        self.sq_width=250
        self.sq_height=80
        self.sub_height=40
        self.root.add(gnomecanvas.CanvasRect, x1 = -(self.sq_width/2)+self.diff,
                    y1 = +(self.sq_height), x2 = +(self.sq_width/2)+self.diff,
                    y2 = +(self.sq_height*2), fill_color = self.loginwin_color)
        self.entrylabel = self.root.add(gnomecanvas.CanvasText,
                    markup = '<span size="10000"><b>Username:</b></span>',
                    fill_color = self.frame_color, x = -33+self.diff, y = 100)
        # entry with small frame
        self.root.add(gnomecanvas.CanvasRect, x1 = -71+self.diff,
                    y1 = 114, x2 = 71+self.diff,
                    y2 = 138, fill_color = self.frame_color)
        self.root.add(gnomecanvas.CanvasWidget, x = -70+self.diff, y = 115,
                    widget = self.entry,
                    width = 140, height = 22)
        self.entry.hide()

    def draw_buttons(self):
        # get the buttons for lang and session selector from the glade tree
        lang_butt = self.wTree.get_widget("lang_button")
        lang_butt.unparent()
        sess_butt = self.wTree.get_widget("sess_button")
        sess_butt.unparent()
        
        # define the button actions
        sess_butt.connect("clicked", lambda w: self.select_session())
        lang_butt.connect("clicked", lambda w: self.select_language())
     
        # draw the lang and session selector
        self.root.add(gnomecanvas.CanvasRect, x1 = -(self.sq_width/2)+self.diff,
                    y1 = +(self.sub_height)+125, x2 = +(self.sq_width/2)+self.diff,
                    y2 = +(self.sub_height*2)+125, fill_color = self.loginwin_color)
        self.root.add(gnomecanvas.CanvasWidget, x = -(self.sq_width/2)+5+self.diff, y = +(self.sub_height)+130,
                    widget = lang_butt,
                    width = 110, height = 32)
        self.root.add(gnomecanvas.CanvasWidget, x = -(self.sq_width/2)+135+self.diff, y = +(self.sub_height)+130,
                    widget = sess_butt,
                    width = 110, height = 32)
        
        # get some additional glade entrys for session and lang selector
        self.sess_box = self.wTree.get_widget("sess_vbox")
        self.sess_box.unparent()
     
        self.lang_box = self.wTree.get_widget("lang_vbox")
        self.lang_box.unparent()

    def draw_shutdown_button(self):
        poweroff = gtk.Button('')
        poweroff.connect("clicked", lambda w: self.shutdown())
        try:
            bg = gtk.gdk.pixbuf_new_from_file(themedir+'/shutdown.png')
            img = gtk.Image()
            img.set_from_pixbuf(bg)
            poweroff.set_image(img)
            poweroff.set_relief('GTK_RELIEF_NONE')
        except:
            poweroff.set_label('I/O')
     
        self.root.add(gnomecanvas.CanvasWidget, x = -((self.width/2)-50), y = (self.height/2)+8,
                widget = poweroff,
                width = 46, height = 46)

    def shutdown(self):
        os.system('/etc/init.d/nbd-client stop')
        os.system('poweroff -fp')

    def draw_bottom_bar(self, show_timer):
        # draw the bottom bar, sysname and timer
        ip = get_ip('eth0')
        sysname = os.uname()
        timew = 0 
        timeh = 0
        
        # get the labels from the glade tree
        self.sysname = self.wTree.get_widget("host_label")
        self.sysname.unparent()
        self.timelabel = self.wTree.get_widget("time_label")
        self.timelabel.unparent()
     
        self.sysname.set_markup('<span size="12000" color="'+self.clockforeground_color+
                    '"><b>'+str(sysname[1])+'</b> ('+str(ip)+') <b>//</b></span>')
     
        # show timer if wanted
        if show_timer == True:
            timew, timeh = self.timelabel.size_request()
            self.timelabel.set_markup(strftime('<span size="12000" color="'+self.clockforeground_color+
                    '">%a %b %d, <b>%H:%M</b></span>', localtime()))
            self.timer = self.root.add(gnomecanvas.CanvasWidget, x = ((self.orig_width/2)-timew), y = (self.height/2)+timeh,
                    widget = self.timelabel,
                    width = timew, height = timeh)
            # refresh the clock all 30 sec
            gobject.timeout_add(30000, lambda: self.update_time())
     
        # get label sizes for drawing
        namew, nameh = self.sysname.size_request()
     
        self.root.add(gnomecanvas.CanvasRect, x1 = -(self.width/2),
                y1 = (self.height/2)+10, x2 = self.width,
                y2 = self.height, fill_color = self.foreground_color)
        self.root.add(gnomecanvas.CanvasWidget, x = ((self.orig_width/2)-namew-timew-10), y = (self.height/2)+nameh,
                widget = self.sysname,
                width = namew, height = nameh)

    def theme_engine(self):
        # draw the window with canvas
        canvas = self.wTree.get_widget("canvas")
        self.win = self.wTree.get_widget("window")
        self.root = canvas.root().add(gnomecanvas.CanvasGroup)
     
        # get screensize
        width, self.height = gtk.gdk.get_default_root_window().get_size()
        self.orig_width = width
        self.width = int(float(width+(width*0.04))) # lets overlap a bit :)
        self.diff = (self.width-self.orig_width)/2  # to make sure the canvas
                                                    # has no grey border
     
        # Theme Engine with sane fallback values 
        # for potentially broken themes
        try:
            # get the background pixmap and scale it to screenwidth
            bg = gtk.gdk.pixbuf_new_from_file(themedir+'/background.png')
            # read colors from theme file
            colors = open(themedir+'/colors')
            colorlist = colors.readlines()
     
            # parse colorlist
            for color in colorlist:
                if string.find(color, "=") > 0 and not color.startswith('#'):
                    val = color.split(' ')[2].rstrip().strip('"')
                    if color.startswith('frame_color'):
                        self.frame_color = val
                    elif color.startswith('foreground'):
                        self.foreground_color = val
                    elif color.startswith('background'):
                        self.background_color = val
                    elif color.startswith('clockforeground'):
                        self.clockforeground_color = val
                    elif color.startswith('loginwin_color'):
                        self.loginwin_color = val
            # put background color up
            self.root.add(gnomecanvas.CanvasRect, x1 = -(self.width/2),
                          y1 = -(self.height/2), x2 = (self.width/2),
                          y2 = (self.height/2)+self.diff, fill_color = self.background_color)
            # put the image on the canvas
            self.root.add(gnomecanvas.CanvasPixbuf, pixbuf = bg,
                          x = -(bg.get_width()/2),
                          y = -(bg.get_height()/2+40), anchor = "nw")
        except:
            # fallback if no theme is available
            self.frame_color = "#000000"
            self.foreground_color = "#101010"
            self.clockforeground_color = "#ffffff"
            self.loginwin_color = "#606060"
     
            self.root.add(gnomecanvas.CanvasRect, x1 = -(self.width/2),
                          y1 = -(self.height/2), x2 = (self.width/2),
                          y2 = (self.height/2)+self.diff, fill_color = "#303030")
            self.root.add(gnomecanvas.CanvasText,
                      markup = '<span size="36000"><b>No Theme Found !</b></span>\n'+
                      '<span size="10000">      Make sure the theme directorys in /usr/share/ldm/themes exist.\n'+
                      '      /usr/share/ldm/themes/default must be a symlink to the\n'+
                      '      directory containing the default theme. Please contact your\n'+
                      '      administrator to fix the ldm installation on the LTSP server.</span>',
                      fill_color = "#606060", x = self.diff, y = -50)
     
            # make sure the window sits correctly on the root
        self.win.set_size_request((self.width), (self.height))
        self.win.set_decorated(False)
     
    def set_cursor(self):
        # set cursor
        self.rootwin = gtk.gdk.get_default_root_window()
        self.rootwin.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))

    def update_time(self):
        # refresh the date/time string
        self.timelabel.set_markup(strftime('<span size="12000" color="'+self.clockforeground_color+
                      '">%a %b %d, <b>%H:%M</b></span>', localtime()))
        return True
	
    def switch_entry(self, flag):
        # switch between username and password entry, make sure neither of them is empty
        # and exit after the password was entered.
        if len(self.entry.get_text()) == 0:
            return
        self.entrylabel.destroy()
        if flag > 0:
            self.password = self.entry.get_text()
            #session = self.session[1]
            #language = self.language[1]
            self.rootwin.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
            self.ok = True
            gtk.main_quit()
        else:
            self.username = self.entry.get_text()
        self.entry.set_text('')
        self.entrylabel = self.root.add(gnomecanvas.CanvasText,
            markup = '<span size="10000"><b>Password:</b></span>',
            fill_color = self.frame_color, x = -33+self.diff, y = 100)
        self.entry.set_visibility(False)
        #self.entry.set_invisible_char('●'.e)
        self.flag = 1

    def select_session(self):
        # session selector - sessions should be read from a config file i.e. /etc/ldm/sessions.conf
        # a Session entry should define the visible name (for the selector) the session manager 
        # command that gets executed on the remote server and optionally a server ip (that gives
        # us xdmcp functionallity for free) which falls back to the bootserver if not set.
        # (indeed there needs to be a entry in known_hosts for a potential other server)
     
        okbutton =  self.wTree.get_widget("sess_ok_button")
        okbutton.connect("clicked", lambda w: destroy_all())
     
        try:
            self.selwin.show()
            self.sess_box.show()
        except:
            self.selwin = self.root.add(gnomecanvas.CanvasWidget, x = -(100), y = 40,
                    widget = self.sess_box,
                    width = 250, height = 200)
            self.selwin.show()
     
            listview =  self.wTree.get_widget("sess_treeview")
            liststore = gtk.ListStore(str, str, str)
            listview.set_model(liststore)
            text = gtk.CellRendererText()
            text1 = gtk.CellRendererText()
            text2 = gtk.CellRendererText()
     
            treeselection = listview.get_selection()
            treeselection.set_mode(gtk.SELECTION_SINGLE)
     
            column = gtk.TreeViewColumn("Sessions available")
     
            column.pack_start(text, False)
            column.pack_start(text1, False)
            column.pack_start(text2, False)
            column.set_attributes(text, markup=1)
     
            listview.append_column(column)
            
            liststore.append([self.session[1], self.session[0], self.session[2]])
            # liststore.append(["session command to be executed", "Visible name", "server ip"])

            sessions = self.get_serverinfo('session')

            if sessions:
                for session in sessions:
                    liststore.append([session, session.split('/')[-1], '127.0.0.1'])
                
            self.sess_box.show()
            listview.grab_focus()
        def destroy_all():
            try:
                list,paths = treeselection.get_selected_rows()[0], treeselection.get_selected_rows()[1:]
                name =  list[paths[0][0]][1].split('\n')
                cmd =  list[paths[0][0]][0].split('\n')
                host =  list[paths[0][0]][2].split('\n')
                self.sess = str(cmd).lstrip('[\'').rstrip('\']')
                self.sess_box.hide()
                self.entry.grab_focus()
            except:
                return

    def select_language(self):
        # the selection of available languages must be server dependent, we should have a file that lists 
        # available languages for every session, what this selector shows should directly depend on the selection
        # made in the session selector above 
        
        okbutton =  self.wTree.get_widget("lang_ok_button")
        okbutton.connect("clicked", lambda w: destroy_all())
        try:
            self.langwin.show()
            self.lang_box.show()
        except:
            self.langwin = self.root.add(gnomecanvas.CanvasWidget, x = -(100), y = -80,
                    widget = self.lang_box,
                    width = 250, height = 400)
            self.langwin.show()
     
            listview =  self.wTree.get_widget("lang_treeview")
            liststore = gtk.ListStore(str, str)
            listview.set_model(liststore)
            text = gtk.CellRendererText()
            text1 = gtk.CellRendererText()
     
            treeselection = listview.get_selection()
            treeselection.set_mode(gtk.SELECTION_SINGLE)
     
            column = gtk.TreeViewColumn("Languages available")
     
            column.pack_start(text, False)
            column.pack_start(text1, False)
            column.set_attributes(text, markup=1)
     
            listview.append_column(column)
            
            liststore.append([self.language[1], self.language[0]])

            langs = self.get_serverinfo('langs')
            if langs:
                for lang in langs:
                    liststore.append([lang, lang])

            self.lang_box.show()
            listview.grab_focus()
        def destroy_all():
            try:
                list,paths = treeselection.get_selected_rows()[0], treeselection.get_selected_rows()[1:]
                name =  list[paths[0][0]][1].split('\n')
                self.language =  list[paths[0][0]][0].split('\n')
                self.lang = str(self.language).lstrip('[\'').rstrip('\']')
                self.lang_box.hide()
                self.entry.grab_focus()
            except:
                return
	
    def get_serverinfo(self, type):
        try:
            ldm_info = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            ldm_info.connect((socket.gethostbyname('server'), 9571))
            sesslist=[]
            langlist=[]

            for line in ldm_info.recv(1024).split('\n'):
                if line.startswith('/'):
                    sesslist.append(line)
                else:
                    if len(line) > 1:
                        langlist.append(line)
            if type == 'session':
                return sesslist
            else:
                return langlist
        except:
            return False

if not GTKGreeter().run():
        sys.exit(1)
