#!/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 time
import hashlib
import sys

from winswitch.util.simple_logger import Logger
from winswitch.consts import SSH_TYPE, X11_TYPE, NX_TYPE, XPRA_TYPE, OSX_TYPE, TYPE_NAMES, MAX_SCREENSHOT_WIDTH, MAX_SCREENSHOT_HEIGHT
from winswitch.util.common import get_elapsed_time_string
from winswitch.ui.config_common import new_config_window, table_init, add_tableline, make_user_menuitem, make_session_menuitem
from winswitch.objects.global_settings import get_settings
from winswitch.objects.session import Session
from winswitch.ui.ui_util import get_ui_util
from winswitch.ui import anims
from winswitch.ui import icons

ALLOW_SOUND_LOOPS = "--allow-sound-loops" in sys.argv

EMBARGO_DURATION=8			#session status updates should cancel the embargo quicker than that
SHOW_SECONDS_UNTIL = 120	#refresh every second for the first 2 minutes

DEBUG_REFRESH = False		#Adds some debug logging to events/redraw

STATUS_ANIMS = {Session.STATUS_CONNECTING : "busy",
				Session.STATUS_SUSPENDING : "busy",
				Session.STATUS_STARTING : "busy",
				}
STATUS_ICONS = {Session.STATUS_AVAILABLE : "pause",
			Session.STATUS_CONNECTED : "play",
			Session.STATUS_IDLE : "play",
			Session.STATUS_SUSPENDED : "pause",
			Session.STATUS_CLOSED : "stop",
			}


session_info_windows = {}

def _get_session_info_key(server, session):
	return	"%s/%s" % (server.ID, session.ID)

def show_session_info_window(applet, server, session):
	"""
	Show the SessionInfoWindow for this session, creating it if necessary.
	"""
	key = _get_session_info_key(server, session)
	global session_info_windows
	session_info = session_info_windows.get(key)
	if not session_info or session_info.window is None:
		session_info = SessionInfoWindow(applet, server, session)
		session_info_windows[key] = session_info
	session_info.unhide()

def close_all_session_info_windows():
	"""
	Closes all the SessionInfoWindows
	"""
	global session_info_windows
	for win in session_info_windows.values():
		win.close_window()



class SessionInfoWindow:
	"""
	Shows session information, grouped by display (so shadow sessions are grouped with their parent)
	"""

	def __init__(self, applet, server, session):
		Logger(self, log_colour=Logger.CRIMSON)
		self.settings = get_settings()
		self.applet = applet
		self.server = server
		self.session = session				#currently selected session
		self.sessions = None					#list of sessions we select from
		self.ui_util = get_ui_util()
		self.last_populate = 0
		self.uuids = []				#for finding who we send to when clicking
		self.embargo = 0
		self.embargo_status = None
		self.sound_embargo = False
		self.reassign_send_to = True
		self.refresh_fast = False
		self.refresh_timer = None
		self.button_signature = None		#use to decide if the buttons have changed and we need to re-draw them
		self.vbox = None
		self.window = self.create_window()
		self.refresh_due = False
		self.schedule_form_refresh()
		self.populate_session_list()
		self.do_populate_session_form()
		self.add_update_notification_callbacks()
		self.window.show()
		w,h = self.vbox.size_request()
		self.sdebug("size_request()=%sx%s" % (w,h), applet, server, session)
		mw = max(w+16, 560)
		mh = max(h+16, 480)
		self.window.set_geometry_hints(self.window, min_width=mw, min_height=mh)

	def create_window(self):
		window = new_config_window("%s" % self.session.name)

		#Contents
		vbox = gtk.VBox(False, 2)
		#Top title
		self.session_top_label = self.ui_util.make_label("Session", "sans 16")
		self.session_top_label.set_size_request(320, 24)
		vbox.pack_start(self.session_top_label)
		#Title bar:
		self.title_bar = gtk.HBox(False, 2)
		# icon
		icon = self.ui_util.get_session_window_icon(self.server, self.session)
		if icon:
			image = self.ui_util.scaled_image(icon, 48)
			image.set_size_request(52,52)
			self.title_bar.pack_start(image)
		self.screenshot_image = gtk.Image()
		self.screenshot_image.set_size_request(MAX_SCREENSHOT_WIDTH, MAX_SCREENSHOT_HEIGHT)
		self.title_bar.pack_start(self.screenshot_image);
		# select session box: shown when there are shadows of the same session
		self.select_session_box = gtk.VBox(False, 0)
		self.select_session = gtk.OptionMenu()
		self.select_session.connect("changed", self.session_changed)
		self.select_session_label = self.ui_util.make_label("This session has shadows:", "sans 13", line_wrap=True)
		align = gtk.Alignment(0.5, 1.0, 0.0, 0.0)
		align.add(self.select_session_label)
		self.select_session_box.add(align)
		align = gtk.Alignment(0.5, 0.0, 0.0, 0.0)
		align.add(self.select_session)
		self.select_session_box.add(align)
		self.select_session_box.set_size_request(180, 48)
		self.title_bar.pack_start(self.select_session_box)
		vbox.pack_start(self.title_bar)

		self.info_text = self.ui_util.make_label("", "sans 12", False, True)
		self.info_text.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("orange"))
		vbox.pack_start(self.info_text)

		# info on left, actions on right:
		hbox = gtk.HBox(False, 20)
		vbox.pack_start(hbox)

		table = table_init(self)
		self.server_button = self.ui_util.make_imagebutton("", "empty", "Server Configuration", self.edit_server)
		self.server_state_image = gtk.Image()
		server_info_box = gtk.HBox(False, 0)
		server_info_box.pack_start(self.server_state_image)
		server_info_box.pack_start(self.server_button)
		add_tableline(self, table, "Server", server_info_box, "The server this session is running on")
		self.started = gtk.Label()
		add_tableline(self, table, "Started", self.started, "When this session was started")
		self.command = gtk.Label()
		add_tableline(self, table, "Command", self.command, "The initial command which started this session")
		self.session_type_icon = gtk.Image()
		self.session_type = gtk.Label()
		type_box = gtk.HBox(False, 6)
		type_box.pack_start(self.session_type_icon)
		type_box.pack_start(self.session_type)
		add_tableline(self, table, "Type", type_box, None)
		self.status = gtk.Label()
		self.status_icon = gtk.Image()
		status_box = gtk.HBox(False, 0)
		status_box.pack_start(self.status)
		status_box.pack_start(self.status_icon)
		add_tableline(self, table, "Status", status_box, None)
		(self.screen_size_label, self.screen_size) = add_tableline(self, table, "Screen Size", gtk.Label(), None)
		#Sound
		sound_box = gtk.HBox(False, 0)
		self.speaker_button = self.ui_util.make_imagebutton("", "speaker_on", "", self.speaker_clicked, min_size=32)
		self.microphone_button = self.ui_util.make_imagebutton("", "microphone_on", "", self.microphone_clicked, min_size=32)
		self.clone_remote_button = self.ui_util.make_imagebutton("", "clone_remote_sound_on", "", self.clone_remote_clicked, min_size=32)
		self.clone_local_button = self.ui_util.make_imagebutton("", "clone_local_sound_on", "", self.clone_local_clicked, min_size=32)
		sound_box.pack_start(self.speaker_button)
		sound_box.pack_start(self.microphone_button)
		sound_box.pack_start(self.clone_remote_button)
		sound_box.pack_start(self.clone_local_button)
		add_tableline(self, table, "Sound sharing", sound_box)
		#Owner
		self.owner_button = self.ui_util.make_imagebutton("", "empty", "View Owner Information Page", self.view_owner)
		self.owner_button_label = gtk.Label()
		owner_box = gtk.HBox(False, 0)
		owner_box.pack_start(self.owner_button)
		owner_box.pack_start(self.owner_button_label)
		add_tableline(self, table, "Owner", owner_box, "The user who started this application")
		#Actor
		self.actor_button = self.ui_util.make_imagebutton("", "empty", "View Actor Information Page", self.view_actor)
		self.actor_button_label = gtk.Label()
		self.actor_box = gtk.HBox(False, 0)
		self.actor_box.pack_start(self.actor_button)
		self.actor_box.pack_start(self.actor_button_label)
		(self.actor_label, _) = add_tableline(self, table, "In use by", self.actor_box, "The user currently connected to this session")

		#options:
		client_util = self.applet.client_utils.get(self.session.session_type)
		if client_util:
			server_command = self.server.get_command_by_uuid(self.session.command_uuid)
			def option_changed(key, value):
				self.sdebug("current options=%s" % self.session.options, key, value)
				if self.session.options.get(key)!=value:
					self.session.options[key] = value
					if self.session and self.session.actor==self.settings.uuid:
						self.info_text.set_text("You must re-connect this session to apply the new settings")
						gobject.timeout_add(10*1000, self.info_text.set_text, "")
			option_widgets = client_util.get_options_widgets(self.server, server_command, None, self.session.options, None, option_changed)
			if len(option_widgets)>0:
				for label,widget in option_widgets:
					add_tableline(self, table, label, widget)

		(self.address_label, self.address) = add_tableline(self, table, "Address", gtk.Label(), "Connection point of this session")
		(self.ID_label, self.ID) = add_tableline(self, table, "ID", self.ui_util.make_label("", "courier 8", True), None)
		(self.display_label, self.display) = add_tableline(self, table, "Display", gtk.Label(), None)
		(self.tunnel_label, self.tunnel) = add_tableline(self, table, "SSH Tunnel", gtk.Label(), None)
		self.tunnel.set_line_wrap(True)
		(self.client_pid_label, self.client_pid) = add_tableline(self, table, "Client PID", gtk.Label(), None)
		(self.last_updated, self.last_updated_label) = add_tableline(self, table, "Last Updated", gtk.Label(), None)
		self.debug_mode_only = [self.address_label, self.address, self.ID_label, self.ID, self.display_label, self.display,
							self.tunnel_label, self.tunnel, self.client_pid_label, self.client_pid, self.last_updated, self.last_updated_label]


		if not self.session.is_shadow() and self.session.session_type not in [SSH_TYPE, X11_TYPE, OSX_TYPE, OSX_TYPE]:
			#send to
			self.send_to_box = gtk.HBox()
			self.send_to = gtk.OptionMenu()
			self.send_to.set_size_request(160, 32)
			self.send_to_box.add(self.send_to)
			self.send_button = self.ui_util.make_imagebutton("Send", "message", None, self.send)
			self.send_to_box.add(self.send_button)
			add_tableline(self, table, "Send to", self.send_to_box, None)
		else:
			self.send_to = None
		hbox.pack_start(table, expand=True, fill=True)
		self.button_box = gtk.VBox(True, 0)
		self.button_box.set_size_request(160, 240)
		hbox.pack_start(self.button_box, expand=False)

		self.vbox = vbox
		self.vbox.show_all()
		window.set_border_width(15)
		window.add(vbox)
		#window.set_geometry_hints(window, min_width=320, min_height=240)
		window.connect('delete_event', self.close_window)
		self.ui_util.add_close_accel(window, self.close_window)
		self.last_populate = 0
		self.uuids = []
		return	window

	def send(self, widget, *args):
		try:
			self.do_send()
		except Exception, e:
			self.serr(None, e, widget, args)

	def get_selected_uuid(self):
		index = self.send_to.get_history()
		uuids = self.uuids
		if index<0 or index>=len(uuids):
			return	None
		return	uuids[index]

	def do_send(self):
		uuid = self.get_selected_uuid()
		if uuid:
			self.applet.send(self.server, self.session, uuid, False)

	def edit_server(self, *args):
		#import locally to prevent import loop...
		from winswitch.ui.config_server import edit_server_config
		edit_server_config(self.applet, self.server, self.refresh, False)

	def view_owner(self, *args):
		self.view_user(self.session.owner)

	def view_actor(self, *args):
		self.view_user(self.session.actor)

	def view_user(self, uuid):
		user = self.server.get_user_by_uuid(uuid)
		self.slog("user=%s" % user, uuid)
		from winswitch.ui.user_page import show_user_info_window
		def send_message(message):
			self.applet.send_message_to_user(self.server, uuid, message)
		show_user_info_window(user, send_message)

	def may_show_user(self, user, session):
		if user.uuid==self.settings.uuid and not self.settings.show_send_to_self:
			return	False
		return	user.can_access_session(session)

	def populate_form(self, run_again=True):
		if DEBUG_REFRESH:
			self.debug()
		self.refresh_due = False
		if not self.window:
			self.cancel_timer()
			return	False
		self.populate_session_list()
		return	self.populate_session_form(run_again)

	def populate_session_form(self, run_again=True):
		if not self.window:
			return	False
		#session form
		uptime = time.time()-self.session.start_time
		fast = self.session.start_time>0 and uptime<SHOW_SECONDS_UNTIL and uptime>=0
		if self.refresh_fast != fast:
			self.schedule_form_refresh()
			run_again = False		# we re-schedule with a different timer
		self.do_populate_session_form()
		return	run_again

	def do_populate_session_form(self):
		#get the real session (X11 in case of shadowing) to show the screenshot:
		real_session = self.session
		if (self.session.shadowed_display):
			real_session = self.server.get_session_by_display(self.session.shadowed_display)
		if real_session:
			icon = real_session.get_screen_capture_icon()
		else:
			icon = self.session.get_screen_capture_icon()
		if icon:
			self.screenshot_image.show()
			self.screenshot_image.set_size_request(icon.get_width()+16, icon.get_height()+4)
			self.screenshot_image.set_from_pixbuf(icon)
			self.title_bar.set_size_request(52+2+MAX_SCREENSHOT_WIDTH+2+160, MAX_SCREENSHOT_HEIGHT)
		else:
			self.screenshot_image.hide()
			self.title_bar.set_size_request(52+2+160, 48+2+2)
		self.session_top_label.set_text(self.session.get_description())
		icon_size = 20
		if self.session.start_time>0:
			uptime = time.time()-self.session.start_time
			fast = self.session.start_time>0 and uptime<SHOW_SECONDS_UNTIL and uptime>=0
			self.started.set_text(get_elapsed_time_string(self.session.start_time, show_seconds=fast))
		else:
			self.started.set_text("n/a")
		icon = self.ui_util.get_server_type_icon(self.server.type, False)
		image = self.ui_util.scaled_image(icon, icon_size)
		self.server_button.set_image(image)
		self.server_button.set_label("%s" % self.server.get_display_name())
		if self.server.is_connected():
			icon = icons.get("connect")
			label = "Connected"
		else:
			icon = icons.get("disconnected")
			if self.server.is_connecting():
				label = "Connecting"
			else:
				label = "Not Connected"
		self.server_state_image.set_from_pixbuf(icon)
		self.server_state_image.set_tooltip_text(label)
		#self.server_button.set_tooltip_text("Edit")
		#tooltip += self.server.type
		#tooltip = ""
		#if self.session.read_only:
		#	tooltip = "read-only "
		def get_display_command():
			cmd = self.session.command
			if cmd.startswith("/"):
				cmds = cmd.split(" ")
				e = cmds[0]
				pos = e.rfind("/")
				if pos>0:
					cmds[0] = e[pos+1:]
					return	" ".join(cmds)
			return cmd
		self.command.set_text(get_display_command())
		icon = icons.try_get(self.session.session_type)
		if icon:
			scaled = icon.scale_simple(icon_size,icon_size,gtk.gdk.INTERP_BILINEAR)
			self.session_type_icon.set_from_pixbuf(scaled)
			self.session_type_icon.set_tooltip_text(self.session.session_type)
		else:
			self.session_type_icon.clear()
		type_descr = TYPE_NAMES.get(self.session.session_type) or self.session.session_type
		self.session_type.set_text(type_descr)
		self.status.set_text("%s " % self.session.status)
		self.set_status_icon()
		self.screen_size.set_text(self.session.screen_size or "unknown")
		show_size = self.session.full_desktop or self.session.shadowed_display or self.session.session_type not in [NX_TYPE, XPRA_TYPE]
		if show_size:
			self.screen_size_label.show()
			self.screen_size.show()
		else:
			self.screen_size_label.hide()
			self.screen_size.hide()

		#Sound:
		self.set_sound_icons()
		#Owner
		owner = self.server.get_user_by_uuid(self.session.owner)
		if owner:
			self.owner_button_label.set_text("  %s" % self.ui_util.get_user_visible_name(owner))
		else:
			self.owner_button_label.set_text("unknown")
		if owner and owner.avatar_icon_data:
			scaled = self.ui_util.scaled_image(owner.get_avatar_icon(), icon_size)
			self.owner_button.set_image(scaled)
			self.owner_button.show()
		else:
			self.owner_button.hide()
		#Actor
		if not self.session.can_have_actor():
			self.actor_label.hide()
			self.actor_box.hide()
		else:
			self.actor_label.show()
			self.actor_box.show()
			actor = self.server.get_user_by_uuid(self.session.actor)
			if actor:
				self.actor_button_label.set_text("  %s" % self.ui_util.get_user_visible_name(actor))
			else:
				self.actor_button_label.set_text("no one")
			if actor and actor.avatar_icon_data:
				scaled = self.ui_util.scaled_image(actor.get_avatar_icon(), icon_size)
				self.actor_button.set_image(scaled)
				self.actor_button.show()
			else:
				self.actor_button.hide()
		#optional section:
		for widget in self.debug_mode_only:
			if self.settings.debug_mode:
				widget.show()
			else:
				widget.hide()
		if self.settings.debug_mode:
			self.address.set_text("%s:%s" % (self.session.host, self.session.port))
			self.ID.set_text(self.session.ID)
			self.display.set_text(self.session.display)
			if self.session.tunnels:
				self.tunnel.set_text("%s tunnels" % len(self.session.tunnels))
			else:
				self.tunnel.set_text("None")
			if self.session.client_pid>0:
				self.client_pid.set_text("%s" % self.session.client_pid)
			else:
				self.client_pid.set_text("None")
			self.last_updated_label.set_text("%s" % time.ctime(self.session.last_updated))

		#send session to action:
		if self.send_to:
			# first figure out if the uuid list has changed (we don't want to re-assign the menu and
			# lose the selection if the user has clicked on the drop down)
			reassign_user_menu = self.reassign_send_to		#we may be forced (ie: when window re-created)
			self.reassign_send_to = False
			if not reassign_user_menu:
				existing_uuids = self.uuids[:]
				for user in self.server.get_active_users():
					if self.may_show_user(user, self.session):
						if user.uuid not in existing_uuids:
							reassign_user_menu = True
							break
						existing_uuids.remove(user.uuid)
				if len(existing_uuids)>0:
					reassign_user_menu = True

			if reassign_user_menu:
				uuids = []
				menu = gtk.Menu()
				selected_uuid = self.get_selected_uuid()
				selected_index = -1
				index = 0
				for user in self.server.get_active_users():
					if not self.may_show_user(user, self.session):
						continue
					if user.uuid == selected_uuid:
						selected_index = index
					menu.append(make_user_menuitem(user))
					uuids.append(user.uuid)
					index += 1
				self.uuids = uuids
				self.send_button.set_sensitive(len(self.uuids)>0)

				#remove options+button
				self.send_to_box.remove(self.send_to)
				self.send_to_box.remove(self.send_button)
				#re-add them... (tried other options - didn't work)
				self.send_to = gtk.OptionMenu()
				self.send_to.set_menu(menu)
				self.send_to.set_size_request(160, 32)
				if selected_index>=0:
					self.send_to.set_history(selected_index)
				self.send_to_box.add(self.send_to)
				self.send_to_box.add(self.send_button)
				self.send_to_box.show_all()
		self.populate_buttons()
		self.last_populate = time.time()
		w = 580
		h = 380
		if self.settings.debug_mode:
			h += 100
		if self.window:		#may have gone...
			self.window.set_size_request(w, h)
		return	False			#dont run again if called from timer

	def session_changed(self, *args):
		self.slog(None, args)
		self.refresh_once(True)

	def show_session_select(self):
		if len(self.sessions)<2:
			self.select_session_box.set_size_request(0,0)
			self.select_session_box.hide()
			self.select_session_box.queue_resize()
		else:
			self.select_session_box.set_size_request(180,92)
			self.select_session_box.show_all()
			self.select_session_box.queue_resize()

	def populate_session_list(self):
		mod = False
		#find out if list has changed
		old_list = self.sessions
		if self.session.session_type==SSH_TYPE:
			#SSH sessions are never shadowed
			self.sessions = [self.session]
			self.show_session_select()
			return
		display = self.get_real_display(self.session)
		self.sessions = self.server.get_all_sessions_for_display(display)
		if old_list is None or len(old_list)!=len(self.sessions):
			mod = True
		else:
			for sess in self.sessions:
				if sess not in old_list:
					mod = True
					#ensure we get notified on this new session too
					self.add_session_update_notification_callback(sess)
		#find out if the selected session has changed
		old_session = self.session
		if len(self.sessions)>0:
			selected_index = self.select_session.get_history()
			if selected_index>=0 and selected_index<len(self.sessions):
				self.session = self.sessions[selected_index]
			else:
				self.session = None
		if self.session is None and len(self.sessions)>0:
			self.session = self.pick_session()
		if not mod:
			mod = old_session != self.session
		if mod:
			self.end_embargo_populate_buttons()
		if len(self.sessions)<2:
			self.show_session_select()			#no point in selecting from one option
			return
		if not mod:
			return		#nothing changed

		menu = gtk.Menu()
		index = 0
		selected_index = -1
		if not self.session and len(self.sessions)>0:
			self.session = self.sessions[0]
		for sess in self.sessions:
			if sess == self.session:
				selected_index = index
			item = make_session_menuitem(sess)
			menu.append(item)
			item.show()
			index += 1
		self.select_session.set_menu(menu)
		if selected_index>0:
			self.select_session.set_history(selected_index)
		self.show_session_select()

	def get_real_display(self, session):
		if session.is_shadow():
			return	session.shadowed_display
		else:
			return	session.display

	def pick_session(self):
		"""
		When there are multiple sessions shown for the same session (main + shadows),
		prefer the one that is more likely to be of interest to us.
		"""
		if len(self.sessions)>1:
			#prefer one we are actor on:
			for sess in self.sessions:
				if sess.actor==self.settings.uuid:
					return	sess
			#or owner:
			for sess in self.sessions:
				if sess.owner==self.settings.uuid:
					return	sess
		return self.sessions[0]

	def set_status_icon(self):
		animation =	STATUS_ANIMS.get(self.session.status)
		if animation:
			self.status_icon.set_from_animation(anims.get(animation))
			return
		icon = STATUS_ICONS.get(self.session.status)
		if icon==None:
			icon = "empty"
		self.status_icon.set_from_pixbuf(self.ui_util.scale_icon(icons.get(icon), 16))

	def set_sound_icons(self):
		sound_session = self.session
		if self.session.shadowed_display:
			sound_session = self.server.get_session_by_display(self.session.shadowed_display)
		assert sound_session

		def set_image(button, basename, live, settings_enabled, server_enabled, session_enabled, tooltip_off, tooltip_on, clone_blocked):
			#self.sdebug("live=%s" % live, button, basename, process, settings_enabled, server_enabled)
			if live:
				on_off = "off"
				tooltip = tooltip_off
				can_toggle = True					#always allow switch off
			else:
				on_off = "on"
				tooltip = tooltip_on
				can_toggle = False
				if not self.server.supports_sound:
					tooltip = "Server does not support sound sharing"
				elif not sound_session.can_do_sound():
					tooltip = "%s sessions do not support sound" % TYPE_NAMES.get(self.session.session_type)
				elif sound_session.status in [Session.STATUS_CLOSED, Session.STATUS_CONNECTING, Session.STATUS_SUSPENDING, Session.STATUS_STARTING]:
					tooltip = "session is %s: sound cannot be enabled" % self.session.status
				elif not settings_enabled:
					tooltip = "Sound is disabled in system configuration"
				elif not server_enabled:
					tooltip = "Sound is disabled in server configuration"
				elif not session_enabled:
					tooltip = "This session does not support this sound option"
				elif sound_session.is_local_display(self.settings.uuid, self.server.local):
					tooltip = "This is your local display, creating sound pipes can create sound loops!"
					can_toggle = ALLOW_SOUND_LOOPS
				elif clone_blocked:
					tooltip = "Cannot be enabled as this would create a sound loop - try disabling other inputs/outputs first"
				else:
					can_toggle = True

			if self.sound_embargo:
				tooltip = "Sound action in progress - please wait a few seconds\n"+tooltip

			button.set_image(self.ui_util.scaled_image(icons.get("%s_%s" % (basename, on_off)), 16))
			button.set_tooltip_text(tooltip)
			button.set_sensitive(can_toggle and not self.sound_embargo)

		#always use the real session (not shadow) for sound:
		set_image(self.speaker_button,"speaker", sound_session.is_sound_live(True, False),
					self.settings.tunnel_sink, self.server.tunnel_sink, sound_session.can_export_sound,
					"Turn Speaker off\n(stop receiving sound from the remote session)", "Turn Speaker on\n(receive sound from the remote session)",
					sound_session.is_sound_live(False, True))		#cant start receiving remote sound if local sound is already clone there (LOOP!)
		set_image(self.microphone_button,"microphone", sound_session.is_sound_live(False, False),
					self.settings.tunnel_source, self.server.tunnel_source, sound_session.can_import_sound,
					"Turn Microphone off\n(stop sending local microphone sound to the remote session)", "Turn Microphone on\n(send local microphone sound to the remote session)",
					sound_session.is_sound_live(True, True))		#cant send microphone if remote sound is already clone here (LOOP!)
		set_image(self.clone_remote_button,"clone_remote_sound", sound_session.is_sound_live(True, True),
					self.settings.tunnel_sink, self.server.tunnel_sink and self.server.tunnel_clone, sound_session.can_export_sound and sound_session.can_clone_sound,
					"Stop receiving cloned sound from the remote session", "Clone Remote Sound\n(start receiving all sound from the remote session)",
					sound_session.is_sound_live(False, False))		#cant clone remote sound is microphone is sent here (LOOP!)
		set_image(self.clone_local_button,"clone_local_sound", sound_session.is_sound_live(False, True),
					self.settings.tunnel_source and self.settings.tunnel_clone, self.server.tunnel_source and self.server.tunnel_clone, sound_session.can_import_sound,
					"Stop sending all local sound to the remote session", "Clone Local Sound\n(start sending all local sound to the remote session)",
					sound_session.is_sound_live(True, False))		#cant clone local sound is remote sound is sent here (LOOP!)

	def embargo_sound(self):
		self.sound_embargo = True
		self.set_sound_icons()
		def lift_sound_embargo():
			self.sdebug()
			if self.window and self.sound_embargo:
				self.sound_embargo = False
				self.set_sound_icons()
		gobject.timeout_add(10*1000, lift_sound_embargo)

	def speaker_clicked(self, *args):
		self.sdebug(None, *args)
		self.embargo_sound()
		self.applet.do_toggle_soundin(self.server, self.session)

	def microphone_clicked(self, *args):
		self.sdebug(None, *args)
		self.embargo_sound()
		self.applet.do_toggle_soundout(self.server, self.session)

	def clone_remote_clicked(self, *args):
		self.sdebug(None, *args)
		self.embargo_sound()
		self.applet.do_toggle_remote_clone(self.server, self.session)

	def clone_local_clicked(self, *args):
		self.sdebug(None, *args)
		self.embargo_sound()
		self.applet.do_toggle_local_clone(self.server, self.session)

	def populate_buttons(self):
		actions = self.get_actions()
		blocked = time.time()<self.embargo
		#calculate new hash:
		sha1 = hashlib.sha1()
		if actions and len(actions)>0:
			for name,_,_,_,enabled in actions:
				sha1.update(name)
				sha1.update("%s" % (enabled and not blocked))
		if self.button_signature == sha1.hexdigest():
			return	False
		#actions:
		for child in self.button_box.get_children():
			self.button_box.remove(child)
		if actions and len(actions)>0:
			for name,tooltip,icon,callback,enabled in actions:
				button = self.make_button(name, tooltip, icon, callback)
				button.set_sensitive(enabled and not blocked)
				button.set_size_request(160, 24)
				self.button_box.pack_start(button)
		self.button_box.show_all()
		self.button_signature = sha1.hexdigest()
		return	False

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

	def callback_delegate(self, x, callback):
		self.sdebug(None, x,callback)
		self.embargo = time.time()+EMBARGO_DURATION
		self.embargo_status = self.session.status
		self.populate_buttons()
		#if the session doesn't fire session_updated() within EMBARDO_DURATION, just redraw buttons as they were
		gobject.timeout_add(EMBARGO_DURATION*1000, self.end_embargo_populate_buttons)
		callback(x)

	def end_embargo_populate_buttons(self):
		if self.window:
			self.embargo = 0
			gobject.idle_add(self.populate_buttons)
		return	False

	def get_actions(self):
		actions = self.applet.get_session_actions(self.server, self.session)
		if len(actions)==0:
			actions.append(("Close this window", None, icons.get("close"), self.close_window, True))
		return	actions


	def show(self):
		self.reassign_send_to = True
		self.button_signature = None
		self.embargo = 0
		self.sessions = None
		if not self.window:
			self.window = self.create_window()
			self.populate_form()
		self.window.show()
		self.schedule_form_refresh()

	def add_update_notification_callbacks(self):
		"""
		Fire callbacks whenever the session or its server is modified
		"""
		self.add_session_update_notification_callback(self.session)
		self.server.add_modified_callback(self.server_modified)
		self.settings.add_modified_callback(self.settings_modified)

	def add_session_update_notification_callback(self, session):
		if session:
			session.add_modified_callback(self.session_modified)

	def session_modified(self):
		self.sdebug()
		if self.embargo>0 and self.session.status!=self.embargo_status:
			self.embargo = 0
		full = self.session.timed_out
		return	self.refresh_once(full)

	def server_modified(self):
		self.sdebug()
		return	self.refresh_once(True)

	def settings_modified(self):
		self.sdebug()
		return	self.refresh_once(True)

	def refresh_once(self, full=True):
		"""
		Ensures that the form is refreshed (in full or just the session part).
		Returns True if this timer/event should fire again, False otherwise.
		(only ever False if the window has been destroyed)
		"""
		if not self.window:
			return	False
		if self.refresh_due:	# or self.refresh_fast:
			return	True		#already pending
		#run populate once, and very soon (100ms)
		if full:
			self.refresh_due = True
			gobject.timeout_add(100, self.populate_form, False)
		else:
			gobject.timeout_add(100, self.do_populate_session_form)
		return	True

	def schedule_form_refresh(self):
		uptime = time.time()-self.session.start_time
		self.refresh_fast = self.session.start_time>0 and uptime<SHOW_SECONDS_UNTIL and uptime>=0
		self.cancel_timer()
		delay = 1000
		if not self.refresh_fast:
			delay = 60*1000
		if DEBUG_REFRESH:
			self.sdebug("refresh_fast=%s, delay=%d" % (self.refresh_fast, delay))
		self.refresh_timer = gobject.timeout_add(delay, self.populate_session_form)

	def cancel_timer(self):
		if self.refresh_timer:
			gobject.source_remove(self.refresh_timer)


	def refresh(self):
		self.show()
		self.unhide()

	def close_window(self, *args):
		self.destroy_window()
		return True

	def destroy_window(self, *args):
		if self.window:
			self.window.destroy()
			self.window = None

	def unhide(self):
		self.show()
		self.window.present()
