#!/usr/bin/python

import os
import sys
import signal
import gtk
import gtk.glade
import gobject
import pwd
import subprocess

class ControlPanel:
    oldlist = ""

    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.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.vnc = self.wTree.get_widget("vncbutton")
	self.vnc.set_sensitive(False)

	self.execute = self.wTree.get_widget("exec")
	self.execute.set_sensitive(False)
	#self.execute.connect("clicked", lambda w: gtk.main_quit())

	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.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()
	
	gobject.timeout_add(500, lambda: self.populate_list(self.liststore))

	self.win.show_all()

    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 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 = []
	list = os.popen('ps eaxww|grep "bash -c env LTSP_CLIENT"|grep -v grep')
	for line in list:
	    pid = os.popen('grep PPid /proc/'+line.split()[0]+'/status').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
   
    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>')

	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

    def poll_proclist(self, user):
	"""
	return the users procs
	"""
	procs = []
	proclist = os.popen('ps --no-headers -o pid,comm:30,%mem,%cpu,nice -aU '+user)
	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('grep') and not comm.startswith('ps'):
	        procs.append(proc.rstrip().split())
	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 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) 
	    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()												
