#!/usr/bin/python

import os
import sys
import signal
import gtk
import gtk.glade
import gobject
import pwd
import subprocess
import dbus
import dbus.service
import dbus.glib
import string

class SCPObject(dbus.service.Object):

    def __init__(self, bus_name, object_path="/SCPObject"):
        dbus.service.Object.__init__(self, bus_name, object_path)

    @dbus.service.signal("com.ubuntu.StudentControlPanel.Comm", signature="aas")
    def GotSignal(self, message):
        pass

class ControlPanel:
    oldlist = ""
    user = []

    def __init__(self):
        """
        Draw the main window and fill the list/treestores
        """
        self.wTree=gtk.glade.XML ("/usr/share/student-control-panel/student-control-panel.glade")
        self.win = self.wTree.get_widget("window")
	self.command = self.wTree.get_widget("command")
	self.message = self.wTree.get_widget("message")

        self.win.connect("destroy", lambda w: gtk.main_quit())
        
        # check if we have root rights exit if not 
        if not self.check_uid():
            self.win.set_sensitive(False)
            dialog = gtk.MessageDialog(self.win,
                    gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
					gtk.MESSAGE_ERROR, gtk.BUTTONS_CANCEL, None)
            dialog.set_markup('<big><b>This tool needs administrative system access</b>\nPlease run it with sudo or gksudo</big>')
            resp = dialog.run()
            if resp:
				sys.exit(1)

	self.cur_user = ''
                    
        self.vnc = self.wTree.get_widget("vncbutton")
        self.vnc.set_sensitive(False)
        
        self.execute = self.wTree.get_widget("exec")
        self.execute.connect("clicked", lambda w: self.start_app(treeselection))

        self.smess = self.wTree.get_widget("mess")
        self.smess.connect("clicked", lambda w: self.send_mess(treeselection))
        
        self.logout = self.wTree.get_widget("logout")
        
        self.userlist = self.wTree.get_widget("studenttree")
        self.userlist.set_property('headers_visible', False)
        self.userlist.set_property('rules_hint', True)
        treeselection = self.userlist.get_selection()
        treeselection.set_mode(gtk.SELECTION_MULTIPLE)

	self.command_ok = self.wTree.get_widget('command_ok')
	self.command_cancel = self.wTree.get_widget('command_cancel')

	self.command_ok.connect("clicked", lambda w: self.comm_ok(treeselection))
	self.command_cancel.connect("clicked", lambda w: self.comm_cancel())
        
	self.command.connect("delete_event", self.close_command)

	self.message_ok = self.wTree.get_widget('message_ok')
	self.message_cancel = self.wTree.get_widget('message_cancel')

	self.message_ok.connect("clicked", lambda w: self.mess_ok(treeselection))
	self.message_cancel.connect("clicked", lambda w: self.mess_cancel())
        
	self.message.connect("delete_event", self.close_mess)

	self.message.hide_all()
	
        self.logout.connect("clicked", lambda w: self.logout_user(treeselection))
        self.vnc.connect("clicked", lambda w: self.vnc_conn(treeselection))
        treeselection.connect("changed", lambda w: self.populate_procs(treeselection, False))
        
        self.liststore = gtk.ListStore(str, str, 'gboolean')
        
        self.userlist.set_model(self.liststore)
        
        text = gtk.CellRendererText()
        pix = gtk.CellRendererPixbuf()
        
        column = gtk.TreeViewColumn("Students")
        
        column.pack_start(pix, False)
        column.pack_start(text, False)
        
        column.set_attributes(text, markup=1)
        
        self.userlist.append_column(column)
        
        pbuf = gtk.gdk.pixbuf_new_from_file('/usr/share/student-control-panel/nobody.png')
        pix.set_property('pixbuf', pbuf)
        
        self.init_proclist()

	self.kill = self.wTree.get_widget("kill")
	procselection = self.proctree.get_selection()
        #procselection.set_mode(gtk.SELECTION_MULTIPLE)
        self.kill.connect("clicked", lambda w: self.kill_proc(procselection))
        
        gobject.timeout_add(500, lambda: self.populate_list(self.liststore))
	gobject.timeout_add(500, lambda: self.refresh_procs())
        
        self.win.show_all()

	system_bus = dbus.SystemBus()
	name = dbus.service.BusName("com.ubuntu.StudentControlPanel", bus=system_bus)
	self.dbus_iface = SCPObject(name)

	self.userlist.connect("button_press_event", lambda w, y: self.on_treeview_button_press_event(w, y))

        self.menu_items = '''<ui>
    <popup name="Context">
      <menuitem action="Start Application"/>
      <menuitem action="Send Message"/>
      <menuitem action="Lockdown"/>
      <menu action="Plugins">
      </menu>
      <menuitem action="Disconnect"/>
    </popup>
    </ui>'''

        self.actiongroup = gtk.ActionGroup('UIManagerExample')
        self.actiongroup.add_actions([
				('Disconnect', None, '_Disconnect', None,
				'Disconnect', lambda x: self.logout_user(self.userlist.get_selection())),
				('Lockdown', None, '_Lockdown', None,
				'Lockdown', self.lockdown),
				('Start Application', None, '_Start Application', None,
				'Start Application', lambda x: self.start_app(self.userlist.get_selection())),
				('Send Message', None, 'Send _Message', None,
				'Send Message', lambda x: self.send_mess(self.userlist.get_selection())),
				('Plugins', None, '_Plugins')])
        self.uimanager=gtk.UIManager()
        self.uimanager.insert_action_group(self.actiongroup, 0)
        self.uimanager.add_ui_from_string(self.menu_items)
        self.menubar = self.uimanager.get_widget('/Context')
	self.plugin_register()

    def get_user_list(self, selection):
	paths = []
	users = []
	list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
	for row in paths[0]:
             users.append(list[row[0]][1].split('\n')[0].lstrip('<b>').rstrip('</b>'))
	return users

    def plugin_register(self):

	reg2 = []
	pluger = os.listdir('/usr/lib/python2.4/site-packages/studentcontrolpanel/plugins')

	for plugin in pluger:
	    if plugin.find('__init__')==-1 and plugin.find('.pyc')==-1 and plugin.find('~')==-1:
	      components = string.split(plugin, '.')
#    print imp.find_module(components[0], '/home/pete/scp/bzrscp/plugins/')
	      try:
	        mod = __import__("studentcontrolpanel.plugins." + components[0])
	      except ImportError:
	         print "There was an error"
		 continue

	      tmpreg=getattr(mod, 'plugins')

	      reg=getattr(tmpreg, components[0])

	      try:
		clname = reg.register()['plugin_class']
	      except AttributeError:
		print "This is not a valid plugin and will be skipped"
	        continue

	      reg2.append(getattr(reg, clname))

	      components[0] = components[0].replace('_',' ')

	      self.uimanager.add_ui(self.uimanager.new_merge_id(),'/Context/Plugins', components[0], components[0], 'menuitem',False)
	      self.actiongroup.add_action(gtk.Action(components[0], components[0], components[0], None))
      	      self.uimanager.get_widget('/Context/Plugins/'+components[0]).connect("activate", self.plugin_wrapper, reg, clname)

    def plugin_wrapper(self, widget, module, clname):
	tmpcl=getattr(module, clname)
	tmpcl(self.get_user_list(self.userlist.get_selection()))

    def on_treeview_button_press_event(self, treeview, event):
      if len(self.get_user_list(self.userlist.get_selection())) != 1:
	      self.uimanager.get_widget('/Context/Lockdown').set_sensitive(False)
      else:
	      self.uimanager.get_widget('/Context/Lockdown').set_sensitive(True)
      if event.button == 3:
          x = int(event.x)
          y = int(event.y)
          time = event.time
          pthinfo = treeview.get_path_at_pos(x, y)
          if pthinfo is not None:
              path, col, cellx, celly = pthinfo
              treeview.grab_focus()
              self.menubar.popup( None, None, None, event.button, time)
          return 1

    def lockdown(self, widget):
	user = self.get_user_list(self.userlist.get_selection())[0]
	subprocess.Popen(['sudo', 'pessulus', '-u', user, '-p', 'xml:readwrite:/home/' + user + '/.gconf/', '-m', 
'xml:readwrite:/var/lib/gconf/users/gconf.xml.mandatory/'])


    def populate_list(self, liststore):
        """
        Get the list of running LTSP sessions and 
        (re)populate the gui if the contents changed
        """
        newlist = self.poll_userlist()
    
        if not newlist == self.oldlist:
           liststore.clear()
           for entry in newlist:
               liststore.append([entry[0], '<b>'+entry[1]+'</b>\n'+entry[-1], True])
           self.oldlist = newlist
	
        return True

    def refresh_procs(self):
	if self.cur_user != '':
	  procselection = self.proctree.get_selection()
	  flag = procselection.count_selected_rows()
          list,paths = procselection.get_selected_rows()[0], procselection.get_selected_rows()[1:]
	  if self.userlist.get_selection().count_selected_rows():
              list = self.poll_proclist(self.cur_user)
              self.fill_proclist(self.cur_user, list)
	      if flag:
	         procselection.select_path(paths[0][0])
	return True

    def kill_proc(self, selection):
	"""
	Reads the process to be killed from the list and executes the kill command
	"""
        list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
	listutmp = subprocess.Popen(['ps aux| grep "' + list[paths[0][0]][0] + '"| grep "' + self.cur_user + '"| grep -v grep'], shell=True, stdout=subprocess.PIPE)	
	listu = listutmp.stdout.read().split()[1]
	dialog = gtk.MessageDialog(self.win,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)
        dialog.set_markup('<big><b>This will end the selected process any may cause data loss.</b>\nAre you sure you want to do this ?</big>')
		
        self.win.set_sensitive(False)
        dialog.connect("destroy", lambda w: self.win.set_sensitive(True))

        resp = dialog.run()
        if resp:
            if resp == gtk.RESPONSE_OK:
		self.dbus_iface.GotSignal([[self.cur_user],["kill"],[listu]])
		self.populate_procs(self.userlist.get_selection(), False)
            dialog.destroy()


    def poll_userlist(self):
        """
            Returns a matrix of pid and user@host for open inbound ssh based LTSP sessions:
            ([pid0] [user0], [host0]), ([pid1], [user1], [host1]), ...)
        """
        ulist = []
        listtmp = subprocess.Popen(['ps eaxww|grep "bash --login -c"|grep "LTSP_CLIENT"|grep PPID|grep -v grep'],shell=True, stdout=subprocess.PIPE)
	list = listtmp.stdout.readlines()
        for line in list:
           pidtmp = subprocess.Popen(['grep PPid /proc/'+line.split()[0]+'/status'],shell=True,stdout=subprocess.PIPE)
	   pid = pidtmp.stdout.read().split()[1]
           for elem in line.split():
               if elem.startswith('USER='):
                   user = elem.lstrip('USER=')
               elif elem.startswith('SSH_CLIENT='):
                   host = elem.lstrip('SSH_CLIENT=')
           ulist.append([pid, user, host])
        return ulist
	"""
	The code below is used to testing on a non LTSP machine, it is used 
	to give false information, pretending the normal logon is a real login,
	the pid is the pid of the ssh-agent process, change it if you like :)
	
	ulist = []
	ulist.append(["8540","pete","localhost"])
	ulist.append(["14822","john","localhost"])
	return ulist
	"""
    
    def populate_procs(self, selection, flag):
        """
        Get list of runnig processes for user and populate 
        the proctree in the gui if contents changed
        """
        list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
    
        userlabel = self.wTree.get_widget("userlabel")
        sys_entry = self.wTree.get_widget("system")
    
        if selection.count_selected_rows():
            user,host = list[paths[0][0]][1].split('\n')
            stripped_user = user.lstrip('<b>').rstrip('</b>')
	    self.cur_user = stripped_user
    
            userlabel.set_markup('<span font_desc="Sans Italic 18" weight="bold">'+user+'</span>')
            sys_entry.set_markup('<span font_desc="Sans Italic 10">'+host+'</span>')
            list = self.poll_proclist(stripped_user)
            self.fill_proclist(stripped_user, list)
        #return flag
	return True

    def clear_procs(self):
        userlabel = self.wTree.get_widget("userlabel")
        sys_entry = self.wTree.get_widget("system")
	userlabel.set_markup('<span font_desc="Sans Italic 18" weight="bold">...</span>')
	sys_entry.set_markup('<span font_desc="Sans Italic 10">...</span>')
	self.procstore.clear()

    def poll_proclist(self, user):
        """
        return the users procs
        """
        procs = []
        proclisttmp = subprocess.Popen(['ps --no-headers -o pid,cmd:60,%mem,%cpu,nice -U '+user], shell=True, stdout=subprocess.PIPE)
	proclist = proclisttmp.stdout.readlines()
        for proc in proclist:
           comm = proc.rstrip().split()[1]
           if not comm.startswith('ssh') and not comm.startswith('dbus') and not comm.startswith('bash') \
                   and not comm.startswith('-') and not comm.startswith('grep') and not comm.startswith('ps'):
               command=proc.rstrip().split()
               command[1]=str(command[1].split('/')[-1])
               command[2]=command[-3]
               command[3]=command[-2]
               command[4]=command[-1]
               procs.append(command)
        return procs

    def init_proclist(self):
        """
        initialize the proctree
        """
        self.proctree = self.wTree.get_widget("proctree")
        self.proctree.set_property('rules_hint', True)
        self.procstore = gtk.ListStore(str, str, str, str)
        self.proctree.set_model(self.procstore)
        
        ppix = gtk.CellRendererPixbuf()
        
        self.proctree.columns = [None]*4
        self.proctree.columns[0] = gtk.TreeViewColumn('Command')
        self.proctree.columns[1] = gtk.TreeViewColumn('CPU')
        self.proctree.columns[2] = gtk.TreeViewColumn('MEM')
        self.proctree.columns[3] = gtk.TreeViewColumn('Status')
        
        self.proctree.columns[0].pack_start(ppix, False)
        self.proctree.columns[0].set_min_width(300)
        
        for n in range(4):
               self.proctree.append_column(self.proctree.columns[n])
               self.proctree.columns[n].cell = gtk.CellRendererText()
               self.proctree.columns[n].pack_start(self.proctree.columns[n].cell, True)
               self.proctree.columns[n].set_attributes(self.proctree.columns[n].cell, text=n)

    def fill_proclist(self, user, list):
        self.procstore.clear()
        for entry in list:
			self.procstore.append([entry[1], entry[2], entry[3], entry[4]])

    def close_command(self,widget,event):
	self.command.hide_all()
	self.win.set_sensitive(True)
	return True
            
    def start_app(self, selection):
	if selection.count_selected_rows():
		self.command.show_all()
		self.win.set_sensitive(False)
	else:
	   dialog = gtk.MessageDialog(self.win,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK, None)
           dialog.set_markup('<big><b>You must select a user first</b></big>')
		
           self.win.set_sensitive(False)
           dialog.connect("destroy", lambda w: self.win.set_sensitive(True))

           resp = dialog.run()
           if resp:
              if resp == gtk.RESPONSE_OK:
	   	dialog.destroy()

    def comm_ok(self,selection):
	command_entry = self.wTree.get_widget("command_entry")
	paths = []
	users = []
	list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
	for row in paths[0]:
             users.append(list[row[0]][1].split('\n')[0].lstrip('<b>').rstrip('</b>'))

	     self.dbus_iface.GotSignal([users,["exec"],[command_entry.get_text()]])
	     self.populate_procs(self.userlist.get_selection(), False)
	self.command.hide_all()
	self.win.set_sensitive(True)

    def comm_cancel(self):
	self.command.hide_all()
	self.win.set_sensitive(True)	

    def close_mess(self,widget,event):
	self.message.hide_all()
	self.win.set_sensitive(True)
	return True
            
    def send_mess(self, selection):
	if selection.count_selected_rows():
		self.message.show_all()
		self.win.set_sensitive(False)
	else:
	   dialog = gtk.MessageDialog(self.win,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK, None)
           dialog.set_markup('<big><b>You must select a user first</b></big>')
		
           self.win.set_sensitive(False)
           dialog.connect("destroy", lambda w: self.win.set_sensitive(True))

           resp = dialog.run()
           if resp:
              if resp == gtk.RESPONSE_OK:
	   	dialog.destroy()

    def mess_ok(self,selection):
	message_entry = self.wTree.get_widget("message_entry")
	paths = []
	users = []
	list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
	for row in paths[0]:
             users.append(list[row[0]][1].split('\n')[0].lstrip('<b>').rstrip('</b>'))

	     self.dbus_iface.GotSignal([users,["mess"],[message_entry.get_text()]])
	     #self.populate_procs(self.userlist.get_selection(), False)
	self.message.hide_all()
	self.win.set_sensitive(True)

    def mess_cancel(self):
	self.message.hide_all()
	self.win.set_sensitive(True)

    def logout_user(self, selection):
	"""
	    Get the selected rows, show a warning dialog and kill the 
	    matching sshd process for each row if ok was clicked
	"""    
        dialog = gtk.MessageDialog(self.win,
				gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
				gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, None)
        dialog.set_markup('<big><b>This will disconnect all selected Students</b>\nAre you sure you want to do this ?</big>')
		
        self.win.set_sensitive(False)
        dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
	
        resp = dialog.run()
        if resp:
            if resp == gtk.RESPONSE_OK:
                paths = []
                list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
                for row in paths[0]:
                    os.kill(int(list[row[0]][0]), signal.SIGKILL)
		#Following line may be able to be back indented
		    self.clear_procs()
		self.cur_user = ''
            dialog.destroy()
	

    def vnc_conn(self, selection):
        """
        Open a VNC connection to the selected host on the list for 
        multiple selected hosts take the first from the list
        """
        list,paths = selection.get_selected_rows()[0], selection.get_selected_rows()[1:]
        err = ''
        if selection.count_selected_rows():
            host = list[paths[0][0]][1].split('\n')[1]
        
            #if not host.startswith('localhost'): # ... to avoid eternal feedback loop
            self.win.set_sensitive(False)
            """
            Create and show a dialog to get the -shared paramater for vnc
            """
            dialog = gtk.MessageDialog(self.win,
                    gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                    gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK, None)
            vbox = gtk.VBox()
            frame = gtk.Frame()
            radio1 = gtk.RadioButton(None, 'Take over Keyboard and Mouse', '-shared')
            radio2 = gtk.RadioButton(radio1, 'Spymode only', '-viewonly')
        
            vbox.pack_start(radio1, False, True, 2)
            vbox.pack_start(radio2, False, True, 2)
            frame.set_property('border_width', 10)
            frame.add(vbox)
        
            dialog.set_markup('<big><b>Select Connection Method</b></big>\nSelect method to connect to the desktop\non '+host)
        
            dialog.vbox.pack_start(frame, False, False, 0)
        
            dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
            dialog.show_all()
        
            pipe_read, pipe_write = os.pipe()
            resp = dialog.run()
            if resp:
                dialog.destroy()
                switch = '-viewonly'
                if radio1.get_active():
                    switch = '-shared'
        
                command = ['/usr/bin/vncviewer', switch, host+':0']
                vnc = subprocess.Popen(command, stderr=subprocess.PIPE).pid
                while gtk.events_pending():
                    gtk.main_iteration(True)
                pid = vnc.pid
                pid = os.fork()
        
                if pid == 0:
                    """
                    Fork vncviewer
                    """
                    sys.stdin.close()
                    command = ['vncviewer', switch, host+':0']
                    sys.stdout.flush()
            
                    print "vnc command line:", command
            
                    os.execvp('vncviewer', command)
            
                    self.win.set_sensitive(False)
                    dialog = gtk.MessageDialog(self.win,
                        gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                        gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, None)
                    dialog.set_markup('<big><b>Connection Failed</b>\nCould not connect to desktop on '+host+'\nmake sure remote desktop sharing is enabled on this host.</big>')
                    dialog.connect("destroy", lambda w: self.win.set_sensitive(True))
            
                    resp = dialog.run()
                if resp:
                    dialog.destroy()
                print os.waitpid(pid, 0)
                gobject.timeout_add(2500, lambda: self.check_proc(pid))
                #fh = os.fdopen(pipe_read)
    def check_proc(self, pid):
        if pid in gtop.proclist():
            print True
            return True
        print False
        return False

    def check_uid(self):
		if os.getuid() == 0:
			return True
		return False

    def main(self):
		gtk.main()

if __name__ == "__main__":
    base = ControlPanel()
    base.main()
