#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import gobject
import gtk
import webbrowser

from winswitch.util.file_io import get_server_config_filename
from winswitch.util.config import save_server_config, modify_server_config
from winswitch.consts import LIBVIRT_TYPE, VIRTUALBOX_TYPE, WINDOWS_TYPE, DETAILED_CONNECT_HELP_URL, SPEEDS, SPEED_NAMES, APPLICATION_NAME
from winswitch.globals import WIN32
from winswitch.util.common import get_elapsed_time_string, csv_list
from winswitch.objects.common import ALLOW_PRINTER_SHARING, ALLOW_FILE_SHARING
from winswitch.ui.config_common import table_init, check_number, add_tableline, keyfile_entry, make_user_menuitem, set_filename, get_filename, add_table_sep
from winswitch.ui.config_common import TabbedConfigureWindow
from winswitch.ui.session_info import show_session_info_window
from winswitch.ui.user_page import create_user_form_box, populate_user_form
from winswitch.util.icon_util import get_icon_from_data
from winswitch.ui import icons
from winswitch.ui import anims


config_server_windows = {}
def get_config_server_windows():
	global config_server_windows
	return	config_server_windows

def edit_server_config(applet, server_config, apply_callback, is_new=False):
	global config_server_windows
	if server_config in config_server_windows:
		csw = config_server_windows[server_config]
		if csw:
			if csw._raise():
				csw.add_apply_callback(apply_callback)
				return			#we managed to raise it to the front - that will do
			else:
				csw.destroy_window()
	csw = ConfigureServerWindow(applet, server_config, apply_callback, is_new)
	config_server_windows[server_config] = csw
	csw.show()


class ConfigureServerWindow(TabbedConfigureWindow):

	def __init__(self, applet, server, apply_callback, is_new):
		TabbedConfigureWindow.__init__(self, "%s Server" % server.get_display_name())
		self.applet = applet
		self.apply_callbacks = [apply_callback]
		self.is_new = is_new
		self.server = server
		self.config_filename = get_server_config_filename(self.server)
		#save old name
		server.previous_name = server.name
		self.selected_user = None
		self.custom_mode_initialized = False
		self.users = []

		self.create()
		self.repopulate_scheduled = False
		self.add_update_notification_callbacks()

	def add_apply_callback(self, callback):
		self.apply_callbacks.append(callback)

	def add_update_notification_callbacks(self):
		"""
		Call us whenever the server or settings is modified
		"""
		self.server.add_modified_callback(self.server_modified)
		self.settings.add_modified_callback(self.settings_modified)


	def make_title_box(self, title, widget=None):
		box = gtk.VBox(False, 0)		#main container we return
		if not title:
			title = self.server.name
		label = self.ui_util.make_label(title, "sans 16")
		icon = self.ui_util.get_server_type_icon(self.server.type)
		hbox = gtk.HBox(False, 10)
		hbox.set_border_width(0)
		if icon:
			image = self.ui_util.scaled_image(icon, 48)
			image.set_size_request(80,80)
			align = gtk.Alignment(0.0, 0.5, 0.0, 1.0)
			align.add(image)
			hbox.pack_start(align)
		align = gtk.Alignment(0.0, 0.5, 0.0, 1.0)
		align.add(label)
		hbox.pack_start(align)
		if widget:
			align = gtk.Alignment(1.0, 0.5, 1.0, 0.0)
			align.add(widget)
			hbox.pack_start(align)
		box.pack_start(hbox)
		return	box

	def create_tabs(self):
		tabs = []
		#GENERAL
		self.general_button = self.ui_util.make_imagebutton("General", "configure")
		self.general_box = self.make_title_box("General")
		table = table_init(self, columns=4)
		(_, self.name_entry) = add_tableline(self, table, "Name", gtk.Entry(max=64),
									"Name of this server as shown in the menu", row_inc=False)
		if not self.is_new:
			(_, self.remote_name_label) = add_tableline(self, table, "Remote Name", gtk.Label(""),
									"The name claimed by this server", x_offset=2)
			self.remote_name_label.set_justify(gtk.JUSTIFY_LEFT)
		hbox = gtk.HBox(False, 0)
		self.platform_image = gtk.Image()
		hbox.add(self.platform_image)
		self.platform = gtk.Label("")
		hbox.add(self.platform)
		(self.platform_label, _) = add_tableline(self, table, "Platform", hbox,
									"The type of operating system on this server", row_inc=False)
		(self.os_version_label, self.os_version) = add_tableline(self, table, "Version", gtk.Label(""),
									"The version of the operating system on this server", x_offset=2)
		box = gtk.HBox(False, 2)
		self.software_version = gtk.Label("")
		self.software_version.set_tooltip_text("The version of %s running on the server" % APPLICATION_NAME)
		box.add(self.software_version)
		self.supported_protocols_box = gtk.HBox(False, 5)
		box.add(self.supported_protocols_box)
		(self.software_version_label, _) = add_tableline(self, table, "Software Version", box, row_inc=False)
		(_, self.start_time_label) = add_tableline(self, table, "Uptime", gtk.Label(""),
									"How long has this server been running?", x_offset=2)
		self.start_time_label.set_justify(gtk.JUSTIFY_LEFT)
		add_table_sep(self, table, cols=4, preinc=1)
		(_, self.enabled_button) = add_tableline(self, table, "Enabled", gtk.CheckButton(),
									"Disabled servers are not shown in the main menu", row_inc=False)
		(_, self.auto_connect_button) = add_tableline(self, table, "Auto Connect", gtk.CheckButton(),
									"Connect to this server automatically on startup", x_offset=2)
		(self.auto_start_label, self.auto_start_button) = add_tableline(self, table, "Auto Start", gtk.CheckButton(),
									"Try to start this server process when it is not started already", row_inc=False)
		(_, self.auto_resume_button) = add_tableline(self, table, "Auto Resume", gtk.CheckButton(),
									"Re-connect your previous session on re-connection", x_offset=2)
		add_table_sep(self, table, cols=4, postinc=1)
		self.add_session_type_options(table, True)	#creates all 4: preferred_[desktop,session]_type_[combo,warning]
		self.add_main_area(self.general_box, table)
		tabs.append((self.general_button, self.general_box))
		self.register_changes(self.name_entry, self.enabled_button, self.auto_connect_button,
							self.auto_resume_button,
							self.preferred_desktop_type_combo, self.preferred_session_type_combo)

		#SECURITY
		self.security_button = self.ui_util.make_imagebutton("Security", "lock")
		self.security_box = self.make_title_box("Security")
		self.security_box.pack_start(self.ui_util.make_label("Key Fingerprint", "sans 12"), True, True, 0)
		self.security_box.pack_start(self.ui_util.make_label(self.server.key_fingerprint, "courier 11", True), True, True, 0)
		self.key_fingerprint_image = gtk.Image()
		self.key_fingerprint_image.set_from_pixbuf(icons.get("empty"))
		self.security_box.pack_start(self.key_fingerprint_image)
		table = table_init(self)
		#creates: self.tunnel_printer_label, self.tunnel_printer_button, self.tunnel_fs_label, self.tunnel_fs_button:
		self.add_tunnel_printfs_options(table)
		self.add_tunnel_sound_options(table, False)
		(_, self.keyfile_entry) = add_tableline(self, table, "SSH Key File", keyfile_entry(False), "Your private SSH key file")
		(_, self.pub_keyfile_entry) = add_tableline(self, table, "SSH public key file", keyfile_entry(True), "Your public SSH key file")
		self.add_main_area(self.security_box, table)
		tabs.append((self.security_button, self.security_box))
		self.register_changes(self.tunnel_printer_button, self.tunnel_fs_button, self.tunnel_sink_button, self.tunnel_source_button, self.tunnel_clone_button)

		#CONNECTION
		self.connection_button = self.ui_util.make_imagebutton("Connection", "connect")
		self.connection_box = self.make_title_box("Connection")
		#switch whole section:
		self.switch_box = gtk.HBox(False, 0)
		self.custom_connection_label = self.ui_util.make_label("")
		self.custom_connection_warning = self.ui_util.make_label("")
		align = gtk.Alignment(1.0, 0.5, 1.0, 0.0)
		vbox = gtk.VBox()
		vbox.add(self.custom_connection_label)
		vbox.add(self.custom_connection_warning)
		align.add(vbox)
		self.switch_box.pack_start(align)
		self.custom_connection_switch = gtk.CheckButton("Override")
		self.custom_connection_switch.connect("toggled", self.custom_connection_switch_changed)
		self.custom_connection_switch.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("orange"))
		self.custom_connection_switch.set_alignment(0, 0.5)
		self.custom_connection_switch.set_size_request(100, 16)
		self.switch_box.pack_start(self.custom_connection_switch)
		self.connection_box.add(self.switch_box)

		table = table_init(self, columns=3)
		(self.line_speed_label, self.line_speed_combo) = add_tableline(self, table, "Line Speed (Mbps)", gtk.combo_box_new_text(), "The speed of the connection to this server")
		self.line_speed_combo.connect("changed", self.check_sound_option)
		(self.timeout_label, self.timeout_entry) = add_tableline(self, table, "Connection timeout", gtk.Entry(max=4), "Time after which the connection is deemed to have failed (in seconds)")
		self.timeout_entry.set_width_chars(5)
		(self.host_label, self.host_entry) = add_tableline(self, table, "Host", gtk.Entry(max=64), "The host name or IP address to connect to")
		#Command port
		command_port_box = gtk.VBox(True, 0)
		hbox = gtk.HBox()
		self.command_port_auto = gtk.RadioButton(None, "Auto")
		self.command_port_auto.connect("toggled", self.command_port_radio_changed)
		hbox.add(self.command_port_auto)
		self.command_port_auto_current = gtk.Label()
		hbox.add(self.command_port_auto_current)
		command_port_box.pack_start(hbox)
		hbox = gtk.HBox()
		self.command_port_manual = gtk.RadioButton(self.command_port_auto, "Manual: ")
		hbox.add(self.command_port_manual)
		self.command_port_entry = gtk.Entry(max=5)
		self.command_port_entry.set_width_chars(5)
		hbox.add(self.command_port_entry)
		command_port_box.pack_start(hbox)
		(self.command_port_label, _) = add_tableline(self, table, "Command Port", command_port_box, "The port where the %s server is running" % APPLICATION_NAME)
		#default_command_port
		(self.username_label, self.username_entry) = add_tableline(self, table, "Username", gtk.Entry(max=64), "Your username on this server")
		self.username_entry.set_width_chars(16)
		admin_box = gtk.HBox(False, 0)
		self.administrator_login_button = gtk.CheckButton()
		self.administrator_login_button.connect("toggled", self.admin_changed)
		self.administrator_username_entry = gtk.Entry(max=64)
		self.administrator_username_entry.set_width_chars(13)
		admin_box.pack_start(self.administrator_login_button)
		admin_box.pack_start(self.administrator_username_entry)
		(self.administrator_label, _) = add_tableline(self, table, "Administrator login", admin_box, "Log in using administrator privileges")
		(self.password_label, self.password_entry) = add_tableline(self, table, "Password", gtk.Entry(max=64), "The password for this user account")
		self.password_entry.set_visibility(False)
		self.password_entry.connect("activate", self.password_activated)
		#ssh options:
		hbox = gtk.HBox()
		self.ssh_tunnel_button = gtk.CheckButton()
		self.ssh_tunnel_button.connect("toggled", self.tunnel_changed)
		hbox.add(self.ssh_tunnel_button)
		self.port_label = self.ui_util.make_label(" Port")
		hbox.add(self.port_label)
		self.port_entry = gtk.Entry(max=5)
		self.port_entry.set_width_chars(5)
		self.port_entry.set_tooltip_text("The port where the remote SSH server is listening")
		hbox.add(self.port_entry)
		(self.ssh_tunnel_label, _) = add_tableline(self, table, "SSH Tunnelling", hbox, "Tunnel connections using SSH - generally required for distant hosts or hosts behind a firewall")
		(self.preload_label, self.preload_button) = add_tableline(self, table, "Preload SSH Tunnels", gtk.CheckButton(), "Preload the SSH tunnels")
		self.connection_actions_box = gtk.VBox(True, 5)
		self.populate_connection_actions_box()
		table.attach(self.connection_actions_box, 2, 3, 1, self.row-2)
		self.add_main_area(self.connection_box, table)
		tabs.append((self.connection_button, self.connection_box))
		self.register_changes(self.line_speed_combo, self.timeout_entry, self.host_entry,
							self.command_port_auto,
							self.username_entry, self.administrator_login_button, self.administrator_username_entry, self.password_entry,
							self.ssh_tunnel_button, self.preload_button, self.port_entry,
							self.keyfile_entry, self.pub_keyfile_entry)

		if not self.is_new:
			#Actions
			self.actions_button = self.ui_util.make_imagebutton("Shortcuts", "star")
			self.actions_box = self.make_title_box("Shortcuts")
			vbox = gtk.VBox(False, 0)
			#label:
			self.actions_label = self.ui_util.make_label("Session Shortcuts:", "sans 14")
			vbox.add(self.actions_label)
			#session list in a scrollable:
			self.actions_scroll = gtk.ScrolledWindow()
			self.actions_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
			self.actions_scroll.set_size_request(self.width-50, self.height-150)			#couldn't find a better way to make it use more space, even when not full
			self.actions_box_contents = None
			vbox.add(self.actions_scroll)
			self.add_main_area(self.actions_box, vbox, True)
			tabs.append((self.actions_button, self.actions_box))

			#USERS
			self.users_button = self.ui_util.make_imagebutton("Users", "group")
			self.users_box = self.make_title_box("Users")
			vbox = gtk.VBox(False, 0)
			self.user_label = self.ui_util.make_label("", "sans 14")
			self.select_user = gtk.OptionMenu()
			self.select_user.set_size_request(220, self.ui_util.get_icon_size()+16)
			hbox = gtk.HBox(False, 20)
			hbox.add(self.user_label)
			hbox.add(self.select_user)
			vbox.add(hbox)
			vbox.add(gtk.Label(" "))
			alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=0.0)
			alignment.add(gtk.HSeparator())
			vbox.add(alignment)
			self.user_form = create_user_form_box(self.users_box, self.send_message_to_user)
			vbox.add(self.user_form)
			self.add_main_area(self.users_box, vbox)
			self.select_user.connect("changed", self.user_changed)
			tabs.append((self.users_button, self.users_box))

			#SESSIONS
			self.sessions_button = self.ui_util.make_imagebutton("Sessions", "run")
			self.sessions_box = self.make_title_box("Existing Sessions")
			vbox = gtk.VBox(False, 0)
			self.sessions_label = self.ui_util.make_label("Sessions", "sans 14")
			vbox.add(self.sessions_label)
			self.sessions_scroll = gtk.ScrolledWindow()
			self.sessions_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
			self.sessions_scroll.set_size_request(self.width-50, self.height-150)			#couldn't find a better way to make it use more space, even when not full
			self.sessions_scroll_viewport = gtk.Viewport()
			self.sessions_scroll.add(self.sessions_scroll_viewport)
			self.sessions_table = None
			vbox.add(self.sessions_scroll)
			#start buttons:
			self.session_start_actions_box = gtk.HBox(True, 10)
			self.populate_start_sessions_box()
			vbox.add(self.session_start_actions_box)
			self.add_main_area(self.sessions_box, vbox, True)
			tabs.append((self.sessions_button, self.sessions_box))

			#set the default tab
			if not self.server.is_connected():
				self.default_section = self.connection_button
			else:
				self.default_section = self.general_button
		return	tabs

	def custom_connection_switch_changed(self, *args):
		self.sdebug()
		custom = self.custom_connection_switch.get_active()
		self.set_custom_mode(custom)

	def set_custom_mode(self, custom):
		self.sdebug()
		custom = self.custom_connection_switch.get_active()
		options = [self.line_speed_label, self.line_speed_combo,
				self.timeout_label, self.timeout_entry,
				self.host_label, self.host_entry,
				self.command_port_label, self.command_port_auto, self.command_port_auto_current, self.command_port_manual, self.command_port_entry,
				self.username_label, self.username_entry,
				#self.administrator_label, self.administrator_login_button, self.administrator_username_entry,
				#self.password_label, self.password_entry,
				self.ssh_tunnel_label, self.ssh_tunnel_button,
				self.preload_label, self.preload_button,
				self.port_label, self.port_entry]
		#local servers don't require passwords!
		for x in self.password_label, self.password_entry:
			x.set_sensitive(custom or (not self.server.embedded_server and not self.server.local))

		for widget in options:
			widget.set_sensitive(custom)

	def set_buttons_state(self):
		TabbedConfigureWindow.set_buttons_state(self)
		if self.can_apply:
			self.actions_warning.set_text("New settings have not been applied yet!")
			self.actions_warning.show()
			self.actions_warning.queue_resize()
		else:
			self.actions_warning.set_text(" ")
			self.actions_warning.hide()
			self.actions_warning.queue_resize()

	def show_help(self, *args):
		webbrowser.open_new_tab(DETAILED_CONNECT_HELP_URL)

	def populate_connection_actions_box(self):
		#remove existing box contents
		for child in self.connection_actions_box.get_children():
			self.connection_actions_box.remove(child)

		#warning label:
		self.actions_warning = self.ui_util.make_label(" ", line_wrap=True)
		self.actions_warning.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("orange"))
		self.actions_warning.set_size_request(140, -1)
		self.connection_actions_box.add(self.actions_warning)

		#connecting animation:
		if self.server.is_connecting():
			server_status_icon = gtk.Image()
			server_status_icon.set_from_animation(anims.get("busy-large"))
			server_status_icon.show()
			self.connection_actions_box.add(server_status_icon)

		#connect actions:
		actions = self.applet.get_connection_actions(self.server)
		actions.insert(0, ("Help", "Help page on configuring connections", icons.get("information"), self.show_help, True))
		for name,tooltip,icon,callback,enabled in actions:
			button = self.make_button(name, tooltip, icon, callback)
			button.set_sensitive(enabled)
			button.set_size_request(140, 20)
			align = gtk.Alignment(0.5, 0.5, 0.0, 1.0)
			align.add(button)
			self.connection_actions_box.add(align)
			align.show_all()
		self.connection_actions_box.show()


	def populate_actions_box(self):
		if self.is_new:
			return
		#remove existing scroll contents
		if self.actions_box_contents:
			for child in self.actions_scroll.get_children():
				self.actions_scroll.remove(child)

		align = gtk.Alignment(0.5, 0.0, 1.0, 0.0)
		self.actions_box_contents = gtk.VBox(False, 0)
		align.add(self.actions_box_contents)
		self.actions_scroll.add_with_viewport(align)
		#get new list
		server_actions = self.applet.get_server_actions(self.server)
		local_shortcuts = self.applet.get_local_shortcuts(self.server)
		total = len(server_actions) + len(local_shortcuts)
		self.actions_label.set_text("%s Session Shortcuts:" % total)
		for name,tooltip,icon,callback,enabled in server_actions:
			button = self.make_button(name, tooltip, icon, callback)
			button.set_sensitive(enabled)
			self.actions_box_contents.add(button)
		for shortcut in local_shortcuts.keys():
			#the link to the shortcut:
			name,tooltip,icon,callback,enabled = local_shortcuts[shortcut]
			box = gtk.HBox(False, 0)
			button = self.make_button(name, tooltip, icon, callback)
			button.set_size_request(340, -1)
			button.set_sensitive(enabled)
			item_align = gtk.Alignment(0.0, 0.5, 1.0, 0.0)
			item_align.add(button)
			box.add(item_align)
			#link to remove the shortcut:
			button = self.ui_util.make_imagebutton("", "minus", "Remove this shortcut", lambda widget : self.remove_shortcut(shortcut))
			item_align = gtk.Alignment(1.0, 0.5, 0.0, 0.0)
			item_align.add(button)
			box.add(item_align)
			self.actions_box_contents.add(box)
		align.show_all()
		return	False		#run from idle_add just once

	def populate_start_sessions_box(self):
		if self.is_new:
			return
		start_actions = self.applet.get_session_start_actions(self.server)
		#remove existing box contents
		for child in self.session_start_actions_box.get_children():
			self.session_start_actions_box.remove(child)
		#add buttons:
		for name,tooltip,icon,callback,enabled in start_actions:
			button = self.make_button(name, tooltip, icon, callback)
			button.set_sensitive(enabled)
			self.session_start_actions_box.add(button)
		self.session_start_actions_box.show_all()


	def remove_shortcut(self, shortcut):
		self.slog("shortcuts=%s" % csv_list(self.server.local_shortcuts), shortcut)
		self.server.local_shortcuts.remove(shortcut)
		self.server.touch()
		modify_server_config(self.server, ["local_shortcuts"])
		self.populate_actions_box()

	def send_message_to_user(self, message):
		client = self.applet.get_link_client(self.server)
		if not client or not client.handler:
			#WARN USER?
			self.serror("no longer connected to %s" % self.server, message)
			return
		client.handler.send_plain_message(self.selected_user.uuid, "Message from %s" % self.settings.name, message, self.settings.uuid)

	def set_send_message_state(self):
		if self.is_new:
			return
		client = self.applet.get_link_client(self.server)
		can_send = (client is not None) and (client.handler is not None)
		self.users_box.send_message_button.set_sensitive(can_send)

	def tunnel_changed(self, *args):
		self.sdebug(None, *args)
		#local server is configured via main applet options
		#without the tunnel, we can't run commmands (so we can't start the server either)
		self.auto_start_button.set_sensitive(not self.server.local and self.ssh_tunnel_button.get_active())
		self.preload_button.set_sensitive(not self.server.local and self.ssh_tunnel_button.get_active())
		#we dont support administrator logins via ssh (must use direct connection to the server)
		#so disable that when ssh is enabled
		self.administrator_login_button.set_sensitive(not self.ssh_tunnel_button.get_active())
		self.administrator_username_entry.set_sensitive(not self.ssh_tunnel_button.get_active())
		self.administrator_label.set_sensitive(not self.ssh_tunnel_button.get_active())


	def user_changed(self, *args):
		self.sdebug(None, *args)
		index = self.select_user.get_history()
		if index>=0 and index<len(self.users):
			new_user = self.users[index]
			if new_user == self.selected_user:
				return
			self.selected_user = new_user
			populate_user_form(self.users_box, self.selected_user)

	def admin_changed(self, *args):
		self.sdebug(None, *args)
		admin = self.administrator_login_button.get_active()
		if admin and not self.administrator_username_entry.get_text():
			#set a default sensible username for admin login if not set:
			if WIN32:
				username = "Administrator"
			else:
				username = "root"
			self.administrator_username_entry.set_text(username)
		self.administrator_username_entry.set_sensitive(admin)
		if admin:
			self.password_label.set_text("Administrator password")
		else:
			self.password_label.set_text("Password")


	def make_button(self, name, tooltip, icon, callback, icon_size=32):
		return	self.ui_util.imagebutton(name, icon, tooltip, lambda x: self.callback_delegate(x, callback), icon_size=icon_size)

	def callback_delegate(self, x, callback):
		self.sdebug(None, x, callback)
		callback(x)

	def server_modified(self):
		return	self.schedule_repopulate()

	def settings_modified(self):
		return	self.schedule_repopulate()

	def schedule_repopulate(self):
		if not self.window:
			return	False					#dont run again (window is gone)
		if self.repopulate_scheduled:
			return	True					#already due - run again to check again
		self.repopulate_scheduled = True
		gobject.timeout_add(100, self.repopulate_actions_tunnels_session_types)
		return	True						#scheduled - run again to check again

	def repopulate_actions_tunnels_session_types(self):
		self.repopulate_scheduled = False	#clear flag
		if not self.window or self.populate_lock:
			return	False
		try:
			self.populate_lock = True
			self.populate_user_select()
			self.populate_actions_box()
			self.populate_connection_actions_box()
			self.populate_start_sessions_box()
			self.set_tunnel_flags()
			self.populate_session_types()
			self.set_send_message_state()
			self.populate_session_scroll()
		finally:
			self.populate_lock = False
		return	False

	def set_tunnel_flags(self):
		if self.tunnel_printer_button:
			self.tunnel_printer_button.set_active(self.server.tunnel_printer and ALLOW_PRINTER_SHARING)
			self.tunnel_printer_button.set_sensitive(self.settings.tunnel_printer and ALLOW_PRINTER_SHARING)
			self.tunnel_printer_label.set_sensitive(self.settings.tunnel_printer and ALLOW_PRINTER_SHARING)
		self.tunnel_sink_button.set_active(self.server.tunnel_sink and self.server.supports_sound)
		self.tunnel_sink_button.set_sensitive(self.settings.tunnel_sink and self.server.supports_sound)
		self.tunnel_sink_label.set_sensitive(self.settings.tunnel_sink and self.server.supports_sound)
		self.tunnel_source_button.set_active(self.server.tunnel_source and self.server.supports_sound)
		self.tunnel_source_button.set_sensitive(self.settings.tunnel_source and self.server.supports_sound)
		self.tunnel_source_label.set_sensitive(self.settings.tunnel_source and self.server.supports_sound)
		self.tunnel_clone_button.set_active(self.server.tunnel_clone and self.server.supports_sound)
		self.tunnel_clone_button.set_sensitive(self.settings.tunnel_clone and self.server.supports_sound)
		self.tunnel_clone_label.set_sensitive(self.settings.tunnel_clone and self.server.supports_sound)
		self.tunnel_fs_button.set_active(self.server.tunnel_fs and ALLOW_FILE_SHARING)
		self.tunnel_fs_button.set_sensitive(self.settings.tunnel_fs and ALLOW_FILE_SHARING)
		self.tunnel_fs_label.set_sensitive(self.settings.tunnel_fs and ALLOW_FILE_SHARING)
		return	False		#run from idle_add just once

	def get_selected_speed(self):
		text = self.line_speed_combo.get_active_text()
		if text and text in SPEEDS:
			return	SPEEDS[self.line_speed_combo.get_active_text()]
		elif self.server.local:
			return	self.settings.default_lan_speed
		return	self.settings.default_internet_speed

	def check_sound_option(self, *args):
		#sink
		can_sink = self.settings.tunnel_sink and self.server.supports_sound
		if not can_sink:
			self.tunnel_sink_button.set_active(False)
		self.tunnel_sink_label.set_sensitive(can_sink)
		self.tunnel_sink_button.set_sensitive(can_sink)
		#source
		can_source = self.settings.tunnel_source and self.server.supports_sound
		if not can_source:
			self.tunnel_source_button.set_active(False)
		self.tunnel_source_label.set_sensitive(can_source)
		self.tunnel_source_button.set_sensitive(can_source)
		#clone
		can_clone = self.settings.tunnel_clone and self.server.supports_sound
		if not can_clone:
			self.tunnel_clone_button.set_active(False)
		self.tunnel_clone_label.set_sensitive(can_source)
		self.tunnel_clone_button.set_sensitive(can_source)

	def populate_session_types(self):
		#preferred session type: choose from a join of the local and server supported types
		self.session_types = []
		self.desktop_types = []
		for (desktop,types) in [(False,self.session_types), (True,self.desktop_types)]:
			local_types = self.settings.get_available_session_types(desktop_only=desktop)
			server_types = self.server.get_available_session_types(desktop_only=desktop, hide_suboptimal=self.settings.hide_suboptimal_protocols)
			for server_type in server_types:
				if server_type in [LIBVIRT_TYPE,VIRTUALBOX_TYPE] or (server_type==WINDOWS_TYPE and desktop):
					continue					#libvirt cannot be used for starting sessions, RDP cannot be used to start "desktop" sessions (just seamless)
				if server_type in local_types:
					types.append(server_type)
		warn = " None available!\nCheck settings and compatibility"
		if self.server.platform.startswith("win"):
			warn = " None available!\nMicrosoft Windows limitation"

		#Desktop:
		self.populate_option_line(self.preferred_desktop_type_combo, self.desktop_types, self.server.preferred_desktop_type, self.preferred_desktop_type_warning, warn)
		#Session:
		self.populate_option_line(self.preferred_session_type_combo, self.session_types, self.server.preferred_session_type, self.preferred_session_type_warning, warn)
		return	False		#run from idle_add just once

	def command_port_radio_changed(self, *args):
		self.sdebug(None, args)
		auto = self.command_port_auto.get_active()
		self.command_port_entry.set_sensitive(not auto)
		if auto:
			self.command_port_auto_current.show()
		else:
			self.command_port_auto_current.hide()

	def do_populate_form(self):
		self.sdebug()
		server = self.server
		self.enabled_button.set_active(server.enabled)
		self.auto_connect_button.set_active(server.auto_connect)
		self.auto_start_button.set_active(server.auto_start)
		self.tunnel_changed()
		self.auto_start_label.set_sensitive(not server.local)
		self.auto_resume_button.set_active(server.auto_resume)
		self.populate_session_types()
		#printers/sound
		self.set_tunnel_flags()

		self.name_entry.set_text(server.name)
		icon = self.ui_util.scale_icon(get_icon_from_data(icons.do_get_raw_icondata_for_platform(server.platform, "empty")), 16)
		if icon:
			self.platform_image.set_from_pixbuf(icon)
		self.platform.set_text(" %s" % server.platform)
		self.os_version.set_text(server.os_version)
		self.start_time_label.set_text(get_elapsed_time_string(server.start_time, show_seconds=False, suffix=""))
		if not self.is_new:
			if server.remote_name:
				self.remote_name_label.set_text(server.remote_name)
			else:
				self.remote_name_label.set_text("")

		if server.key_fingerprint_image:
			pixbuf = get_icon_from_data(server.key_fingerprint_image)
			self.key_fingerprint_image.set_from_pixbuf(pixbuf)
		self.software_version.set_text(csv_list(server.app_version, sep=".", before="", after=""))
		self.software_version.set_tooltip_text(server.version_info)
		#protocols:
		for child in self.supported_protocols_box.get_children():
			self.supported_protocols_box.remove(child)
		prots = {"xpra" :		("Xpra", self.server.supports_xpra),
				"nx" :			("NX", self.server.supports_nx),
				"ssh" :			("SSH", self.server.supports_ssh),
				"vnc" :			("VNC", self.server.supports_vnc or self.server.supports_vncshadow),
				"rdp" :			("RDP", self.server.supports_rdp or self.server.supports_rdp_seamless),
				"virtualbox" :	("VirtualBox", self.server.supports_virtualbox),
				"screen" :		("GNU Screen", self.server.supports_screen),
				"speaker_on" :	("sound forwarding", self.server.supports_sound),
				"gstvideo" :	("GStreamer Video", self.server.supports_gstvideo)
				}
		for name,(descr, enabled) in prots.items():
			if enabled:
				protocol_image = self.ui_util.scaled_image(icons.get(name), 16)
				protocol_image.set_tooltip_text("supports %s" % descr)
				self.supported_protocols_box.add(protocol_image)
		self.supported_protocols_box.show_all()

		custom_warn = ""
		warning_colour = "orange"
		can_override = True
		if self.server.embedded_server:
			custom_warn = "This is an embedded local server"
			warning_colour = "red"
			can_override = False
		elif self.server.local:
			custom_warn = "This is a local server"
		elif self.server.dynamic:
			custom_warn = "This server was detected by ZeroConf"
		self.custom_connection_warning.set_text(custom_warn)
		self.custom_connection_label.set_text("Automatic settings should be correct")

		if custom_warn:
			gtk_colour = gtk.gdk.color_parse(warning_colour)
			self.custom_connection_warning.modify_fg(gtk.STATE_NORMAL, gtk_colour)
			self.custom_connection_label.modify_fg(gtk.STATE_NORMAL, gtk_colour)
			self.switch_box.set_size_request(self.width-64,64)
			self.switch_box.show_all()
			if not self.custom_mode_initialized:
				self.set_custom_mode(False)
				self.custom_mode_initialized = True
			self.custom_connection_switch.set_sensitive(can_override)
		else:
			self.switch_box.hide()
			self.switch_box.set_size_request(0,0)

		#line speed
		self.line_speed_combo.get_model().clear()
		index = 0
		for speed in sorted(SPEED_NAMES.keys()):
			text = SPEED_NAMES[speed]
			self.line_speed_combo.append_text(text)
			if server.line_speed == speed:
				self.line_speed_combo.set_active(index)
			index += 1
		self.check_sound_option()		#ensures sound can't be selected on slow lines
		self.timeout_entry.set_text("%s" % server.timeout)
		self.host_entry.set_text(server.host)
		#command port:
		self.command_port_auto.set_active(server.command_port_auto)
		self.command_port_manual.set_active(not server.command_port_auto)
		if server.command_port_auto:
			self.command_port_entry.set_text("")
		else:
			self.command_port_entry.set_text("%s" % server.default_command_port)
		self.command_port_radio_changed()
		if server.command_port>0:
			self.command_port_auto_current.set_text("(%s)" % server.command_port)
		else:
			self.command_port_auto_current.set_text(" ")

		self.username_entry.set_text(server.username)
		self.administrator_login_button.set_active(len(server.administrator_login)>0)
		self.administrator_username_entry.set_text(server.administrator_login)
		self.ssh_tunnel_button.set_active(server.ssh_tunnel)
		self.preload_button.set_active(server.preload_tunnels)
		self.port_entry.set_text(str(server.port))
		self.password_entry.set_text(server.password)
		# key file
		set_filename(self.keyfile_entry, server.ssh_keyfile, True)
		set_filename(self.pub_keyfile_entry, server.ssh_pub_keyfile, True)

		#shortcuts:
		if not self.is_new:
			self.populate_actions_box()

		#USERS
		if not self.is_new:
			self.populate_user_select()
			if self.selected_user:
				self.user_form.show()
				populate_user_form(self.users_box, self.selected_user)
			else:
				self.user_form.hide()
		self.set_send_message_state()

		#SESSIONS
		self.populate_session_scroll()

		self.untouch()

	def populate_user_select(self):
		self.users = self.server.get_active_users()
		if not self.users or len(self.users)==0:
			label = "No users found"
			if not self.server.is_connected():
				label += " - not connected."
			self.user_label.set_text(label)
			self.select_user.hide()
			self.user_form.hide()
			return
		self.user_label.set_text("%s users connected:" % len(self.users))
		menu = gtk.Menu()
		index = 0
		selected_index = 0
		if not self.selected_user and len(self.users)>0:
			self.selected_user = self.users[0]
		for user in self.users:
			if user == self.selected_user:
				selected_index = index
			item = make_user_menuitem(user)
			menu.append(item)
			item.show()
			index += 1
		self.select_user.set_menu(menu)
		self.select_user.show_all()
		if selected_index<len(self.users):
			self.select_user.set_history(selected_index)
		populate_user_form(self.users_box, self.selected_user)
		self.user_form.show()

	def make_user_button(self, user):
		def user_clicked(*args):
			self.slog(None, *args)
			self.selected_user = user
			self.populate_user_select()
			populate_user_form(self.users_box, self.selected_user)
			self.show_section(self.users_button)
		return	self.ui_util.imagebutton("", user.get_avatar_icon(), tooltip=self.ui_util.get_user_visible_name(user), clicked_callback=user_clicked, icon_size=24)

	def	populate_session_scroll(self):
		if self.is_new:
			return
		sessions = sorted(self.server.get_live_sessions(False, ignore=[]), key=lambda session: session.name.lower())
		if len(sessions)==1:
			header = "Found 1 session:"
		elif len(sessions)>1:
			header = "Found %d sessions:" % len(sessions)
		else:
			header = "No active sessions found"
			if not self.server.is_connected():
				header += " - not connected."
		self.sessions_label.set_text(header)
		#so we can try to preserve the scroll location:
		va = self.sessions_scroll_viewport.get_vadjustment().value

		#clear existing one first:
		if self.sessions_table:
			for x in self.sessions_scroll_viewport.get_children():
				self.sessions_scroll_viewport.remove(x)

		#create a new one (use a table so we can add labels, more info, etc)
		if len(sessions)==0:
			self.sessions_table = None
			return
		self.sessions_table = gtk.Table(len(sessions), 2)
		align = gtk.Alignment(0.5, 0.0, 1.0, 0.0)
		align.add(self.sessions_table)
		self.sessions_scroll_viewport.add(align)
		col = 0
		for title in ["Application", "Type", "Owner", "Actor", "Actions"]:
			self.sessions_table.attach(self.ui_util.make_label(title, "sans 12"), col, col+1, 0, 1)
			col += 1
		row = 1
		icon_size = 24
		if len(sessions)>8:
			icon_size = 16
		for session in sessions:
			self.populate_session_scroll_item(session, row, icon_size)
			row += 1
		align.show_all()
		vadj = self.sessions_scroll_viewport.get_vadjustment()
		vadj.set_value(max(min(va, vadj.upper), vadj.lower))

	def	populate_session_scroll_item(self, session, row, icon_size):
		#Application
		self.sessions_table.attach(self.get_session_button(session, icon_size), 0, 1, row, row+1)
		#Type
		icon = icons.try_get(session.session_type)
		if icon:
			img = self.ui_util.scaled_image(icon, icon_size)
			img.set_tooltip_text("%s session" % session.session_type)
			self.sessions_table.attach(img, 1, 2, row, row+1)
		#Owner
		owner = self.server.get_user_by_uuid(session.owner)
		if owner and owner.get_avatar_icon():
			button = self.make_user_button(owner)
			self.sessions_table.attach(button, 2, 3, row, row+1)
		#Actor
		actor = self.server.get_user_by_uuid(session.actor)
		if actor and actor.get_avatar_icon():
			button = self.make_user_button(actor)
			self.sessions_table.attach(button, 3, 4, row, row+1)
		#Actions:
		actions = self.applet.get_session_actions(self.server, session)
		action_box = gtk.HBox()
		show_name = len(actions)<3
		for name,tooltip,icon,callback,enabled in actions:
			if not show_name:
				name = ""
			button = self.make_button(name, tooltip, icon, callback, icon_size)
			if not enabled:
				continue
			button.set_sensitive(enabled)
			action_box.add(button)
		self.sessions_table.attach(action_box, 4, 5, row, row+1)



	def get_session_button(self, session, icon_size):
		tooltip = "%s" % session.status
		btn = self.ui_util.imagebutton(session.name, session.get_window_icon(), tooltip, lambda x : self.session_clicked(session), icon_size=icon_size)
		btn.set_size_request(160, self.ui_util.get_icon_size()+8)
		return btn

	def session_clicked(self, session):
		self.sdebug(None, session)
		show_session_info_window(self.applet, self.server, session)

	def _raise(self):
		if self.window and self.window.window:
			self.window.window.show()
			return	True
		return	False

	def password_activated(self, *args):
		self.slog(None, *args)
		self.apply_changes()
		self.applet.retry_server(None, self.server)


	def do_apply_changes(self):
		server = self.server
		server.enabled = self.enabled_button.get_active()
		server.auto_connect = self.auto_connect_button.get_active()
		server.auto_start = self.auto_start_button.get_active()
		server.auto_resume = self.auto_resume_button.get_active()
		# session type
		server.preferred_session_type = self.get_selected_option(self.preferred_session_type_combo, self.session_types)
		# desktop type
		server.preferred_desktop_type = self.get_selected_option(self.preferred_desktop_type_combo, self.desktop_types)
		# filesharing
		server.tunnel_fs = self.tunnel_fs_button.get_active()
		# print
		if self.tunnel_printer_button:
			server.tunnel_printer = self.tunnel_printer_button.get_active()
		# sound
		server.tunnel_sink = self.tunnel_sink_button.get_active()
		server.tunnel_source = self.tunnel_source_button.get_active()
		server.tunnel_clone = self.tunnel_clone_button.get_active()
		server.name = self.name_entry.get_text()
		#line speed
		server.line_speed = self.get_selected_speed()
		try:
			server.timeout = int(self.timeout_entry.get_text())
		except:
			pass
		server.host = self.host_entry.get_text()
		server.command_port_auto = self.command_port_auto.get_active()
		if server.command_port_auto:
			server.default_command_port = 0
		else:
			server.default_command_port = int(self.command_port_entry.get_text())

		server.username = self.username_entry.get_text()
		if self.administrator_login_button.get_active():
			server.administrator_login = self.administrator_username_entry.get_text()
		else:
			server.administrator_login = ""
		server.ssh_tunnel = self.ssh_tunnel_button.get_active()
		server.preload_tunnels = self.preload_button.get_active()
		(valid, server.port) = check_number("Port", self.port_entry.get_text(), 0)
		if not valid:
			self.port_entry.focus()
			return
		server.set_password(self.password_entry.get_text())
		server.ssh_keyfile = get_filename(self.keyfile_entry)
		server.ssh_pub_keyfile = get_filename(self.pub_keyfile_entry)
		server.touch()
		self.call_apply_callbacks()

	def call_apply_callbacks(self):
		for x in self.apply_callbacks:
			if x:
				x()

	def do_commit_changes(self):
		self.destroy_window()

		#delete old config if filename has changed:
		save_server_config(self.server)
		if self.is_new:
			self.applet.add_server(self.server)
