#!/usr/bin/python -OO
#
#   A utility for managing WiFi profiles on GNU/Linux.
#
#   Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
#   Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
#   Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
#   Copyright (C) 2006 David Decotigny <com.d2@free.fr>
#   Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
#   Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
#   Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
#   Copyright (C) 2009-2010 Sean Robinson <seankrobinson@gmail.com>
#   Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; version 2 of the License.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License in LICENSE.GPL for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#	http://wifi-radar.berlios.de
#
#	See CREDITS file for more contributors.
#	See HISTORY file for, well, changes.
#
#	NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to 
#		  turn on console debugging.

import ConfigParser
import errno
import gtk
import logging
import logging.handlers
import os
import Queue
import re
import string
import sys
import tempfile
import threading
from shutil import move
from signal import SIGTERM
from subprocess import call, Popen, PIPE, STDOUT
from time   import sleep
from types  import *

WIFI_RADAR_VERSION = "2.0.s08"

#
# Where the conf file should live could be different for your distro.  Please change
# at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
#
CONF_FILE	 = "/etc/wifi-radar/wifi-radar.conf"

os.environ['LC_MESSAGES'] = 'C'


####################################################################################################
####################################################################################################

# Gets the network interface device
#
#Parameters:
#
#	'device' -- string - The proposed network device to use
#
#Returns:
#
#	string -- The actual network device to use
def get_network_device(device):
	#print "get_network_device: %s" % (device, )
	if device != "auto_detect":
		return confFile.get_opt('DEFAULT.interface')
	else:
		# auto detect network device	
		# Get a list of 802.11 enabled devices by parsing the output of iwconfig.
		# If no devices are found, default to eth1.
		# call iwconfig command and read output
		iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE, stderr=STDOUT).stdout
		wireless_devices = [(x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
		if len(wireless_devices) > 0:
			return wireless_devices[0]
	logger.critical("No WiFi device found, please set this in the preferences.")
	return ""

# Return a blank profile
#
#Parameters:
#
#	none
#
#Returns:
#
#	dictionary -- An AP profile with defaults set.
def get_new_profile():
	return { 'known': False,
			 'available': False,
			 'encrypted': False,
			 'essid': '',
			 'bssid': '',
			 'roaming': False,
			 'protocol': 'g',
			 'signal': -193,
			 'channel': 'auto',
			 'con_prescript': '',
			 'con_postscript': '',
			 'dis_prescript': '',
			 'dis_postscript': '',
			 'key': '',
			 'mode': 'auto',
			 'security': '',
			 'use_wpa': False,
			 'wpa_driver': '',
			 'use_dhcp': True,
			 'ip': '',
			 'netmask': '',
			 'gateway': '',
			 'domain': '',
			 'dns1': '',
			 'dns2': ''
			}

# Combine essid and bssid to make a config file section name
#
#Parameters:
#
#	'essid' -- string - AP ESSID
#
#	'bssid' -- string - AP BSSID
#
#Returns:
#
#	string -- the bssid concatenated to a colon, concatenated to the essid
def make_section_name( essid, bssid ):
	return essid + ':' + bssid

# Split a config file section name into an essid and a bssid
#
#Parameters:
#
#	'section' -- string - Config file section name
#
#Returns:
#
#	list -- the essid and bssid
def split_section_name( section ):
	parts = re.split(':', section)
	return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]

# Run commands through the shell
#
#Parameters:
#
#	'command' -- tuple - The command and arguments to run.
#
#	'environment' -- dictionary - Environment variables (as keys) and their values.
#
#Returns:
#
#	boolean -- True on success, otherwise, False
def shellcmd( command, environment = None ):
	try:
		env_tmp = os.environ
		env_tmp.update(environment)
		command = ' '.join(command)
		return_code = call(command, shell=True, env=env_tmp)
		if return_code >= 0:
			return True
		else:
			print >>sys.stderr, "Child was terminated by signal", -return_code
	except OSError, exception:
		print >>sys.stderr, "Execution failed:", exception
	return False

# Speak feedback message to user
#
#Parameters:
#
#	'words' -- string - Message to speak to user
#
#Returns:
#
#	nothing
def	say( words ):
	if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
	words = words.replace( "\"", "\\\"" )
	shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])

# Scan for a limited time and return AP names and bssid found.
# Access points we find will be put on the outgoing Queue, apQueue.
#
#Parameters:
#
#	'confFile' -- ConfigFile - Config file object
#
#	'apQueue' -- Queue - Queue on which to put AP profiles
#
#	'commandQueue' -- Queue - Queue from which to read commands
#
#	'logger' -- Logger - Python's logging facility
#
#Returns:
#
#	nothing
def scanning_thread(confFile, apQueue, commandQueue, logger, exit_event):
	logger.info("Begin thread.")
	# Setup our essid pattern matcher
	essid_pattern		= re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M  | re.S )
	bssid_pattern		= re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M  | re.S )
	protocol_pattern	= re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M  | re.S )
	mode_pattern		= re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M  | re.S )
	channel_pattern		= re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M  | re.S )
	enckey_pattern		= re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M  | re.S )
	signal_pattern		= re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M  | re.S )
	
	access_points = {}
	command = "scan"
	while True:
		try:
			command = commandQueue.get_nowait()
			logger.info("received command: %s" % (command, ))
			command_read = True
		except Queue.Empty:
			command_read = False
		device = get_network_device(confFile.get_opt('DEFAULT.interface'))
		if command == "scan":
			logger.debug("Beginning scan pass")
			# Some cards need to have the interface up to scan
			if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
				# call ifconfig command and wait for return
				shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), device, 'up'])
			# update the signal strengths
			try:
				scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), device, 'scan'], stdout=PIPE).stdout.read()
			except OSError, (errno, strerror):
				if errno == 2:
					logger.critical("iwlist command not found, please set this in the preferences.")
					scandata = ""
			# zero out the signal levels for all access points
			for bssid in access_points:
				access_points[bssid]['signal'] = 0
			# split the scan data based on the address line
			hits = scandata.split(' - ')
			for hit in hits:
				# set the defaults for profile template
				profile = get_new_profile()
				m = essid_pattern.search( hit )
				if m:
					# we found an essid
					profile['essid'] = m.groups()[1]
					m = bssid_pattern.search( hit )			# get BSSID from scan
					if m: profile['bssid'] = m.groups()[1]
					m = protocol_pattern.search( hit )		# get protocol from scan
					if m: profile['protocol'] = m.groups()[1]
					m = mode_pattern.search( hit )			# get mode from scan
					if m: profile['mode'] = m.groups()[1]
					m = channel_pattern.search( hit )		# get channel from scan
					if m: profile['channel'] = m.groups()[1]
					m = enckey_pattern.search( hit )		# get encryption key from scan
					if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
					m = signal_pattern.search( hit )		# get signal strength from scan
					if m: profile['signal'] = m.groups()[1]
					access_points[ profile['bssid'] ] = profile
			for bssid in access_points:
				access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
				# Put all, now or previously, sensed access_points into apQueue
				try:
					logger.debug("Scanned profile: %s" % (access_points[ bssid ], ))
					apQueue.put_nowait( access_points[bssid] )
				except Queue.Full:
					pass
		if command_read:
			commandQueue.task_done()
		if exit_event.isSet():
			logger.info("Exiting.")
			return
		if device.find('ath') == 0:
				sleep( 3 )
		else:
				sleep( 1 )


# Manage a connection; including reporting connection state, 
# connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
class ConnectionManager():
	# Create a new connection manager which can read a config file and send to scanning thread 
	# command Queue.  A new manager checks for a pre-existing connection and takes
	# its AP profile from the ESSID and BSSID to which it is currently attached.
	#
	#Parameters:
	#
	#	'confFile' -- ConfigFile - Config file object
	#
	#	'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
	#
	#	'logger' -- Logger - Python's logging facility
	#
	#Returns:
	#
	#	ConnectionManager instance
	def __init__( self, confFile, commandQueue, logger ):
		self.confFile = confFile
		self.commQueue = commandQueue
		self.logger = logger
		# is connection running?
		self.state = False
		if self.get_current_ip():
			self.state = True
			self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
	
	# Change the interface state: up or down.
	#
	#Parameters:
	#
	#	'state' -- string - The state to which to change the interface.
	#
	#Returns:
	#
	#	nothing
	def if_change( self, state ):
		if ( (state.lower() == 'up') or (state.lower() == 'down') ):
			self.logger.info("changing interface state to %s" % (state, ))
			# call ifconfig command and wait for return
			shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface')), state])
	
	# Return the WiFi encryption mode from evidence in the profile.
	#
	#Parameters:
	#
	#	'use_wpa' -- boolean - The use_wpa from the profile.
	#
	#	'key' -- string - The WEP key or empty string.
	#
	#Returns:
	#
	#	string - none, wep, or wpa; indicates WiFi encryption mode
	def _get_enc_mode(self, use_wpa, key):
		if use_wpa:
			return 'wpa'
		elif key == '':
			return 'none'
		else:
			return 'wep'
	
	# Connect to the specified AP.
	#
	#Parameters:
	#
	#	'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
	#
	#	'status' -- status implementer - Object which implements status interface.
	#
	#Returns:
	#
	#	nothing
	def connect_to_network( self, profile, status ):
		self.profile = profile
		if self.profile['bssid'] == '':
			raise TypeError("Empty AP address")
		msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
		say( msg )
		self.logger.info(msg)
		# Make a temporary copy of the DEFAULT.interface option.
		default_interface = self.confFile.get_opt('DEFAULT.interface')
		device = get_network_device(self.confFile.get_opt('DEFAULT.interface'))
		# Temporarily set the configured interface to a real one.
		self.confFile.set_opt('DEFAULT.interface', device)
		# ready to dance
		# Let's run the connection prescript
		if self.profile['con_prescript'].strip() != '':
			# got something to execute
			# run connection prescript through shell and wait for return
			self.logger.info("executing connection prescript: %s" % (self.profile['con_prescript'], ))
			shellcmd([self.profile['con_prescript']], environment = { 
						"WIFIRADAR_PROFILE": make_section_name(self.profile['essid'], self.profile['bssid']),
						"WIFIRADAR_ENCMODE": self._get_enc_mode(self.profile['use_wpa'], self.profile['key']),
						"WIFIRADAR_SECMODE": self.profile['security'],
						"WIFIRADAR_IF": device or ''
						}
					)
		status.show()
		# Some cards need to have the interface up
		if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
			self.if_change('up')
		# Start building iwconfig command line, command
		iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
		iwconfig_command.append(device)
		# Setting essid
		iwconfig_command.append( 'essid' )
		iwconfig_command.append( "'" + self.profile['essid'] + "'" )
		# Setting nick
		#iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
		# Setting key
		iwconfig_command.append( 'key' )
		if self.profile['key'] == '':
			iwconfig_command.append( 'off' )
		else:
			# Setting this stops association from working, so remove it for now
			#if self.profile['security'] != '':
				#iwconfig_command.append(self.profile['security'])
			iwconfig_command.append( "'" + self.profile['key'] + "'" )
			#iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
		# Setting mode
		if self.profile['mode'].lower() == 'master' or self.profile['mode'].lower() == 'auto':
			self.profile['mode'] = 'Managed'
		iwconfig_command.append( 'mode' )
		iwconfig_command.append( self.profile['mode'] )
		# Setting channel
		if self.profile['channel'] != '':
			iwconfig_command.append( 'channel' )
			iwconfig_command.append( self.profile['channel'] )
		# Now we do the ap by address (do this last since iwconfig seems to want it only there)
		iwconfig_command.append( 'ap' )
		iwconfig_command.append( self.profile['bssid'] )
		# Some cards require a commit
		if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
			iwconfig_command.append( 'commit' )
		self.logger.info("iwconfig_command: %s" % (iwconfig_command, ))
		# call iwconfig command and wait for return
		if not shellcmd(iwconfig_command): return
		# Now normal network stuff
		# Kill off any existing DHCP clients running
		if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
			self.logger.info("Killing existing DHCP...")
			try:
				if self.confFile.get_opt('DHCP.kill_args') != '':
					# call DHCP client kill command and wait for return
					shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
				else:
					os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
			except OSError:
				print "failed to kill DHCP client"
				sys.exit()
			finally:
				print "Stale pid file.  Removing..."
				os.remove(self.confFile.get_opt('DHCP.pidfile'))
		# Kill off any existing WPA supplicants running
		if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
			self.logger.info("Killing existing WPA supplicant...")
			try:
				if not self.confFile.get_opt('WPA.kill_command') != '':
					# call WPA supplicant kill command and wait for return
					shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
				else:
					os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
			except OSError:
				print "failed to kill WPA supplicant"
				sys.exit()
			finally:
				print "Stale pid file.  Removing..."
				os.remove(self.confFile.get_opt('WPA.pidfile'))
		self.logger.debug("Disable scan while connection attempt in progress...")
		try:
			self.commQueue.put("pause")
		except Queue.Full:
			pass
		# Begin WPA supplicant
		if self.profile['use_wpa'] :
			self.logger.info("WPA args: %s" % (self.confFile.get_opt('WPA.args'), ))
			status.update_message("WPA supplicant starting")
			if sys.modules.has_key("gtk"):
				while gtk.events_pending():
					gtk.main_iteration(False)
			# call WPA supplicant command and do not wait for return
			try:
				wpa_proc = shellcmd([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args')])
				if sys.modules.has_key("gtk"):
					while gtk.events_pending():
						gtk.main_iteration(False)
				sleep(2)
			except OSError, (errno, strerror):
				if errno == 2:
					logger.critical("WPA supplicant not found, please set this in the preferences.")
		if self.profile['use_dhcp'] :
			status.update_message("Acquiring IP Address (DHCP)")
			if sys.modules.has_key("gtk"):
				while gtk.events_pending():
					gtk.main_iteration(False)
			# call DHCP client command and do not wait for return
			dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
			dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
			dhcp_command.append(device)
			self.logger.info("dhcp_command: %s" % (dhcp_command, ))
			try:
				dhcp_proc = Popen(dhcp_command, stdout=None)
			except OSError, (errno, strerror):
				if errno == 2:
					logger.critical("DHCP client not found, please set this in the preferences.")
			timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
			tick = 0.25
			waiting = dhcp_proc.poll()
			while waiting == None:
				waiting = dhcp_proc.poll()
				if timer < 0:
					os.kill(dhcp_proc.pid, SIGTERM)
					break
				if sys.modules.has_key("gtk"):
					while gtk.events_pending():
						gtk.main_iteration(False)
				timer -= tick
				sleep(tick)
			if not self.get_current_ip():
				status.update_message("Could not get IP address!")
				if sys.modules.has_key("gtk"):
					while gtk.events_pending():
						gtk.main_iteration(False)
				sleep(1)
				if self.state:
					self.disconnect_interface()
				status.hide()
				return
			else:
				status.update_message("Got IP address. Done.")
				self.state = True
				if sys.modules.has_key("gtk"):
					while gtk.events_pending():
						gtk.main_iteration(False)
				sleep(2)
		else:
			ifconfig_command= "%s %s down; %s %s %s netmask %s" % ( self.confFile.get_opt('DEFAULT.ifconfig_command'), device, self.confFile.get_opt('DEFAULT.ifconfig_command'), device, self.profile['ip'], self.profile['netmask'] )
			route_command 	= "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
			resolv_contents = ''
			if self.profile['domain'] != '': resolv_contents += "domain %s\n" % self.profile['domain']
			if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % self.profile['dns1']
			if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % self.profile['dns2']
			if ( resolv_contents != '' ):
				resolv_file=open('/etc/resolv.conf', 'w')
				resolv_file.write(resolv_contents)
				resolv_file.close
			if not shellcmd([ifconfig_command]): return
			if not shellcmd([route_command]): return
			self.state = True
		# Re-enable iwlist
		try:
			self.commQueue.put("scan")
		except Queue.Full:
			pass
		# Let's run the connection postscript
		con_postscript = self.profile['con_postscript']
		if self.profile['con_postscript'].strip() != '':
			self.logger.info("executing connection postscript: %s" % (self.profile['con_postscript'], ))
			shellcmd([self.profile['con_postscript']], environment = {
						"WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
						"WIFIRADAR_ESSID": self.get_current_essid() or '',
						"WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
						"WIFIRADAR_PROFILE": make_section_name(self.profile['essid'], self.profile['bssid']),
						"WIFIRADAR_ENCMODE": self._get_enc_mode(self.profile['use_wpa'], self.profile['key']),
						"WIFIRADAR_SECMODE": self.profile['security'],
						"WIFIRADAR_IF": device or ''
						}
					)
		# Set the configured interface back to original value.
		self.confFile.set_opt('DEFAULT.interface', default_interface)
		status.hide()
	
	# Disconnect from the AP with which a connection has been established/attempted.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def disconnect_interface( self ):
		msg = "Disconnecting"
		say( msg )
		self.logger.info(msg)
		# Make a temporary copy of the DEFAULT.interface option.
		default_interface = self.confFile.get_opt('DEFAULT.interface')
		device = get_network_device(self.confFile.get_opt('DEFAULT.interface'))
		# Temporarily set the configured interface to a real one.
		self.confFile.set_opt('DEFAULT.interface', device)
		# Pause scanning while manipulating card
		try:
			self.commQueue.put("pause")
			self.commQueue.join()
		except Queue.Full:
			pass
		if self.state:
			self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), self.get_current_bssid()))
			if not self.profile:
				self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), ''))
				if not self.profile:
					raise KeyError
		# Let's run the disconnection prescript
		if self.profile['dis_prescript'].strip() != '':
			self.logger.info("executing disconnection prescript: %s" % (self.profile['dis_prescript'], ))
			shellcmd([self.profile['dis_prescript']], environment = {
						"WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
						"WIFIRADAR_ESSID": self.get_current_essid() or '',
						"WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
						"WIFIRADAR_PROFILE": make_section_name(self.profile['essid'], self.profile['bssid']),
						"WIFIRADAR_ENCMODE": self._get_enc_mode(self.profile['use_wpa'], self.profile['key']),
						"WIFIRADAR_SECMODE": self.profile['security'],
						"WIFIRADAR_IF": device or ''
						}
					)
		self.logger.info("Kill off any existing DHCP clients running...")
		if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
			self.logger.info("Killing existing DHCP...")
			try:
				if self.confFile.get_opt('DHCP.kill_args').strip() != '':
					dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
					dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
					dhcp_command.append(device)
					self.logger.info("DHCP command: %s" % (dhcp_command, ))
					# call DHCP client command and wait for return
					if not shellcmd(dhcp_command): return
				else:
					self.logger.info("Killing DHCP manually...")
					os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
			except OSError:
				print "failed to kill DHCP client"
		self.logger.info("Kill off any existing WPA supplicants running...")
		if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
			self.logger.info("Killing existing WPA supplicant...")
			try:
				if self.confFile.get_opt('WPA.kill_command') != '':
					wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
					if not shellcmd(wpa_command): return
				else:
					os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
			except OSError:
				print "failed to kill WPA supplicant"
		self.logger.info("Let's clear out the wireless stuff")
		shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), device, 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
		self.logger.info("Now take the interface down")
		self.logger.info("Since it may be brought back up by the next scan, lets unset its IP")
		shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), device, '0.0.0.0'])
		# taking down the interface too quickly can crash my system, so pause a moment
		sleep(1)
		self.if_change('down')
		# Let's run the disconnection postscript
		if self.profile['dis_postscript'].strip() != '':
			self.logger.info("executing disconnection postscript: %s" % (self.profile['dis_postscript'], ))
			shellcmd([self.profile['dis_postscript']], environment = { 
						"WIFIRADAR_PROFILE": make_section_name(self.profile['essid'], self.profile['bssid']),
						"WIFIRADAR_ENCMODE": self._get_enc_mode(self.profile['use_wpa'], self.profile['key']),
						"WIFIRADAR_SECMODE": self.profile['security'],
						"WIFIRADAR_IF": device or ''
						}
					)
		self.state = False
		self.logger.info("Disconnect complete.")
		# Begin scanning again
		try:
			self.commQueue.put("scan")
		except Queue.Full:
			pass
		# Set the configured interface back to original value.
		self.confFile.set_opt('DEFAULT.interface', default_interface)
	
	# Returns the current IP, if any, by calling ifconfig.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	string or None -- the IP address or None (if no there is no current connection)
	def get_current_ip( self ):
		ifconfig_command = [confFile.get_opt('DEFAULT.ifconfig_command'), get_network_device(confFile.get_opt('DEFAULT.interface'))]
		try:
			ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
		except OSError, (errno, strerror):
			if errno == 2:
				logger.critical("ifconfig command not found, please set this in the preferences.")
				ifconfig_info = open("/dev/null", "r")
		# Be careful to the language (inet adr: in French for example)
		#
		# Hi Brian
		# 
		# I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
		# There the string in ifconfig is inet Adresse for the IP which isn't
		# found by the current get_current_ip function in wifi-radar. I changed
		# the according line (#289; gentoo, v1.9.6-r1) to
		# >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
		# which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
		# 
		# I'd be happy if you could incorporate this small change because as now
		# I've got to change the file every time it is updated.
		# 
		# Best wishes
		# 
		# Simon
		ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
		line = ifconfig_info.read()
		if ip_re.search( line ):
			return ip_re.search( line ).group(1)
		return None

	# Returns the current ESSID, if any, by calling iwconfig.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	string or None -- the ESSID or None (if no there is no current association)
	def get_current_essid( self ):
		"""Returns the current ESSID if any by calling iwconfig"""
		try:
			iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface'))], stdout=PIPE, stderr=STDOUT).stdout
		except OSError, (errno, strerror):
			if errno == 2:
				logger.critical("iwconfig command not found, please set this in the preferences.")
				iwconfig_info = open("/dev/null", "r")
		# Be careful to the language (inet adr: in French for example)
		essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M  | re.S )
		line = iwconfig_info.read()
		if essid_re.search( line ):
			return essid_re.search( line ).group(2)
		return None
	
	# Returns the current BSSID, if any, by calling iwconfig.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	string or None -- the BSSID or None (if no there is no current association)
	def get_current_bssid( self ):
		"""Returns the current BSSID if any by calling iwconfig"""
		try:
			iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface'))], stdout=PIPE, stderr=STDOUT).stdout
		except OSError, (errno, strerror):
			if errno == 2:
				logger.critical("iwconfig command not found, please set this in the preferences.")
				iwconfig_info = open("/dev/null", "r")
		bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M  | re.S )
		line = iwconfig_info.read()
		if bssid_re.search( line ):
			return bssid_re.search( line ).group(2)
		return None
	


# The main user interface window for WiFi Radar.  This class also is the control
# center for most of the rest of the operations.
class radar_window:
	# Create a new radar_window.
	#
	#Parameters:
	#
	#	'confFile' -- ConfigFile - The config file in which to store/read settings.
	#
	#	'apQueue' -- Queue - The Queue from which AP profiles are read.
	#
	#	'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
	#
	#	'logger' -- Logger - Python's logging facility
	#
	#Returns:
	#
	#	radar_window instance
	def __init__(self, confFile, apQueue, commQueue, logger, exit_event):
		global	signal_xpm_none
		global	signal_xpm_low
		global	signal_xpm_barely
		global	signal_xpm_ok
		global	signal_xpm_best
		global	known_profile_icon
		global	unknown_profile_icon
		global	wifi_radar_icon
		
		self.confFile = confFile
		self.apQueue = apQueue
		self.commandQueue = commQueue
		self.logger = logger
		self.access_points = {}
		self.exit_event = exit_event
		self.connection = None
		
		self.known_profile_icon		= gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
		self.unknown_profile_icon	= gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
		self.signal_none_pb			= gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
		self.signal_low_pb			= gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
		self.signal_barely_pb		= gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
		self.signal_ok_pb			= gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
		self.signal_best_pb			= gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
		self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
		icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
		self.window.set_icon( icon )
		self.window.set_border_width( 10 )
		self.window.set_size_request( 550, 300 )
		self.window.set_title( "WiFi Radar" )
		self.window.connect( 'delete_event', self.delete_event )
		self.window.connect( 'destroy', self.destroy )
		# let's create all our widgets
		self.current_network = gtk.Label()
		self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
		self.current_network.show()
		self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
		self.close_button.show()
		self.close_button.connect( 'clicked', self.delete_event, None  )
		self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
		self.about_button.show()
		self.about_button.connect( 'clicked', self.show_about_info, None )
		self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
		self.preferences_button.show()
		self.preferences_button.connect( 'clicked', self.edit_preferences, None )
		#                            essid + bssid known_icon      known  available  wep_icon  signal_level    mode  protocol  channel
		self.pstore = gtk.ListStore( str,          gtk.gdk.Pixbuf, bool,  bool,      str,      gtk.gdk.Pixbuf, str,  str,      str )
		self.plist = gtk.TreeView( self.pstore )
		# The icons column, known and encryption
		self.pix_cell 	= gtk.CellRendererPixbuf()
		self.wep_cell 	= gtk.CellRendererPixbuf()
		self.icons_cell	= gtk.CellRendererText()
		self.icons_col	= gtk.TreeViewColumn()
		self.icons_col.pack_start( self.pix_cell, False )
		self.icons_col.pack_start( self.wep_cell, False )
		self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
		self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
		self.plist.append_column( self.icons_col )
		# The AP column
		self.ap_cell	= gtk.CellRendererText()
		self.ap_col	= gtk.TreeViewColumn( "Access Point" )
		self.ap_col.pack_start( self.ap_cell, True )
		self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
		self.plist.append_column( self.ap_col )
		# The signal column
		self.sig_cell 	= gtk.CellRendererPixbuf()
		self.signal_col		= gtk.TreeViewColumn( "Signal" )
		self.signal_col.pack_start( self.sig_cell, True )
		self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
		self.plist.append_column( self.signal_col )
		# The mode column
		self.mode_cell 	= gtk.CellRendererText()
		self.mode_col		= gtk.TreeViewColumn( "Mode" )
		self.mode_col.pack_start( self.mode_cell, True )
		self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
		self.plist.append_column( self.mode_col )
		# The protocol column
		self.prot_cell 	= gtk.CellRendererText()
		self.protocol_col	= gtk.TreeViewColumn( "802.11" )
		self.protocol_col.pack_start( self.prot_cell, True )
		self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
		self.plist.append_column( self.protocol_col )
		# The channel column
		self.channel_cell 	= gtk.CellRendererText()
		self.channel_col	= gtk.TreeViewColumn( "Channel" )
		self.channel_col.pack_start( self.channel_cell, True )
		self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
		self.plist.append_column( self.channel_col )
		# DnD Ordering
		self.plist.set_reorderable( True )
		# detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
		self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
		# enable/disable buttons based on the selected network
		self.selected_network = self.plist.get_selection()
		self.selected_network.connect( 'changed', self.on_network_selection, None )
		# the list scroll bar
		sb = gtk.VScrollbar( self.plist.get_vadjustment() )
		sb.show()
		self.plist.show()
		# Add New button
		self.new_button = gtk.Button( "_New" )
		self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None  )
		self.new_button.show()
		# Add Configure button
		self.edit_button = gtk.Button( "C_onfigure" )
		self.edit_button.connect( 'clicked', self.edit_profile, None  )
		self.edit_button.show()
		self.edit_button.set_sensitive(False)
		# Add Delete button
		self.delete_button = gtk.Button( "_Delete" )
		self.delete_button.connect('clicked', self.delete_profile_with_check, None)
		self.delete_button.show()
		self.delete_button.set_sensitive(False)
		# Add Connect button
		self.connect_button = gtk.Button( "Co_nnect" )
		self.connect_button.connect( 'clicked', self.connect_profile, None  )
		# Add Disconnect button
		self.disconnect_button = gtk.Button( "D_isconnect" )
		self.disconnect_button.connect( 'clicked', self.disconnect_profile, None  )
		# lets add our widgets
		rows		= gtk.VBox( False, 3 )
		net_list	= gtk.HBox( False, 0 )
		listcols	= gtk.HBox( False, 0 )
		prows		= gtk.VBox( False, 0 )
		# lets start packing
		# the network list
		net_list.pack_start( self.plist, True, True, 0 )
		net_list.pack_start( sb, False, False, 0 )
		# the rows level
		rows.pack_start( net_list , True, True, 0 )
		rows.pack_start( self.current_network, False, True, 0 )
		# the list columns
		listcols.pack_start( rows, True, True, 0 )
		listcols.pack_start( prows, False, False, 5 )
		# the list buttons
		prows.pack_start( self.new_button, False, False, 2 )
		prows.pack_start( self.edit_button, False, False, 2 )
		prows.pack_start( self.delete_button, False, False, 2 )
		prows.pack_end( self.connect_button, False, False, 2 )
		prows.pack_end( self.disconnect_button, False, False, 2 )

		self.window.action_area.pack_start( self.about_button )
		self.window.action_area.pack_start( self.preferences_button )
		self.window.action_area.pack_start( self.close_button )

		rows.show()
		prows.show()
		listcols.show()
		self.window.vbox.add( listcols )
		self.window.vbox.set_spacing( 3 )
		self.window.show_all()
		#
		# Now, immediately hide these two.  The proper one will be
		# displayed later, based on interface state. -BEF-
		self.disconnect_button.hide()
		self.connect_button.hide()
		self.connect_button.set_sensitive(False)
		
		# set up connection manager for later use
		self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
		# set up status window for later use
		self.status_window = StatusWindow( self )
		self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
		
		# Add our known profiles in order
		for profile_name in self.confFile.auto_profile_order:
			profile_name = profile_name.strip()
			self.access_points[profile_name] = self.confFile.get_profile(profile_name)
			wep = None
			if self.access_points[profile_name]['encrypted']:
				wep = gtk.STOCK_DIALOG_AUTHENTICATION
			if self.access_points[profile_name]['roaming']:
				ap_name = self.access_points[profile_name]['essid'] + "\n" + '  Multiple APs'
			else:
				ap_name = self.access_points[profile_name]['essid'] + "\n" + self.access_points[profile_name]['bssid']
			self.pstore.append([ap_name, self.known_profile_icon, self.access_points[profile_name]['known'], self.access_points[profile_name]['available'], wep, self.signal_none_pb, self.access_points[profile_name]['mode'], self.access_points[profile_name]['protocol'], self.access_points[profile_name]['channel'] ] )
		# This is the first run (or, at least, no config file was present), so pop up the preferences window
		if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
			self.confFile.remove_option('DEFAULT', 'new_file')
			self.edit_preferences(self.preferences_button)

	# Begin running radar_window in Gtk event loop.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def main( self ):
		gtk.main()

	# Quit application.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#Returns:
	#
	#	nothing
	def destroy( self, widget = None):
		if self.status_window:
			self.status_window.destroy()
		gtk.main_quit()

	# Kill scanning thread, update profile order for config file, and ask to be destroyed.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	boolean -- always return False (i.e. do not propigate the signal which called)
	def delete_event( self, widget, data = None ):
		# Let other threads know it is time to exit
		self.exit_event.set()
		# Wait for all other threads to exit before continuing
		while threading.activeCount() > 1:
			sleep(0.25)
		self.destroy()
		return False

	# Update the current ip and essid shown to user.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def update_network_info(self):
		if self.connection and self.connection.state:
			self.current_network.set_text( "Connected to %s\nIP Address %s" % (make_section_name(self.connection.get_current_essid(), self.connection.get_current_bssid()), self.connection.get_current_ip()))
		else:
			self.current_network.set_text("Not Connected.")
	
	# Set the state of connect/disconnect buttons based on whether we have a connection.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def update_connect_buttons(self):
		if self.connection and self.connection.state:
			self.connect_button.hide()
			self.disconnect_button.show()
		else:
			self.disconnect_button.hide()
			self.connect_button.show()
	
	# Updates the on-screen profiles list.
	#
	#Parameters:
	#
	#	'ap' -- dictionary -- The AP found by scanning_thread.
	#
	#Returns:
	#
	#	nothing
	def update_plist_items(self, ap):
		# Check for roaming profile.
		ap_name = make_section_name(ap['essid'], '')
		profile = self.confFile.get_profile(ap_name)
		prow_iter = self.get_row_by_ap(ap['essid'], '  Multiple APs')
		ap_display = ap['essid'] + "\n" + '  Multiple APs'
		if not profile:
			# Check for normal profile.
			ap_name = make_section_name(ap['essid'], ap['bssid'])
			profile = self.confFile.get_profile(ap_name)
			prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
			ap_display = ap['essid'] + "\n" + ap['bssid']
			if not profile:
				# No profile, so make a new one.
				profile = get_new_profile()
		wep = None
		if prow_iter != None:
			# the AP is in the list of APs on the screen
			# Set the 'known' values; False is default, overridden to True by self.access_points
			ap['known'] = self.access_points[ap_name]['known']
			self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
			self.pstore.set_value(prow_iter, 2, ap['known'])
			self.pstore.set_value(prow_iter, 3, ap['available'])
			if ap['encrypted']:
				wep = gtk.STOCK_DIALOG_AUTHENTICATION
			self.pstore.set_value(prow_iter, 4, wep)
			self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(self.access_points[ap_name]['signal']))
			self.pstore.set_value(prow_iter, 6, ap['mode'])
			self.pstore.set_value(prow_iter, 7, ap['protocol'])
			self.pstore.set_value(prow_iter, 8, self.access_points[ap_name]['channel'])
		else:
			# the AP is not in the list of APs on the screen
			self.pstore.append([ap_display, self.pixbuf_from_known(ap['known']), ap['known'], ap['available'], wep, self.pixbuf_from_signal(ap['signal']), ap['mode'], ap['protocol'], ap['channel']])
			#print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']

	# Updates the record-keeping profiles list.
	#
	#Parameters:
	#
	#	'ap' -- dictionary -- The AP found by scanning_thread.
	#
	#Returns:
	#
	#	nothing
	def update_ap_list(self, ap):
		# Check for roaming profile.
		ap_name = make_section_name(ap['essid'], '')
		profile = self.confFile.get_profile(ap_name)
		if not profile:
			# Check for normal profile.
			ap_name = make_section_name(ap['essid'], ap['bssid'])
			profile = self.confFile.get_profile(ap_name)
			if not profile:
				# No profile, so make a new one.
				profile = get_new_profile()
		if self.access_points.has_key(ap_name):
			# This AP has been configured and should be updated
			self.access_points[ap_name]['known']		= profile['known']
			self.access_points[ap_name]['available']	= ap['available']
			self.access_points[ap_name]['encrypted']	= ap['encrypted']
			self.access_points[ap_name]['mode']			= ap['mode']
			self.access_points[ap_name]['protocol']		= ap['protocol']
			if self.access_points[ap_name]['roaming']:
				# Roaming
				if self.access_points[ap_name]['bssid'] == ap['bssid']:
					# Same AP for this roaming profile
					self.access_points[ap_name]['signal']	= ap['signal']
				else:
					# Different AP
					if int(self.access_points[ap_name]['signal']) < int(ap['signal']):
						# Stronger signal with this AP, so promote it to preferred
						self.access_points[ap_name]['signal']	= ap['signal']
						self.access_points[ap_name]['channel']	= ap['channel']
						self.access_points[ap_name]['bssid']	= ap['bssid']
			else:
				# Not roaming
				self.access_points[ap_name]['signal']	= ap['signal']
				self.access_points[ap_name]['channel']	= ap['channel']
		else:
			# Not seen before, begin tracking it.
			self.access_points[ap_name] = profile
	
	# Updates the main user interface.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	boolean -- always return True
	def update_window(self):
		# Indicate to PyGtk that only one Gtk thread should run here
		gtk.gdk.threads_enter()
		self.update_network_info()
		self.update_connect_buttons()
		while True:
			# Get APs scanned by iwlist
			try:
				ap = self.apQueue.get_nowait()
			except Queue.Empty:
				break
			else:
				self.update_ap_list(ap)
				self.update_plist_items(ap)
		# Allow other Gtk threads to run
		gtk.gdk.threads_leave()
		return True
	
	# Return the proper icon for a value of known.
	#
	#Parameters:
	#
	#	'known' -- boolean - Whether the AP is known (i.e. configured)
	#
	#Returns:
	#
	#	gtk.gdk.Pixbuf -- icon for a known or unknown AP
	def pixbuf_from_known( self, known ):
		""" return the proper icon for value of known """
		if known:
			return self.known_profile_icon
		else:
			return self.unknown_profile_icon

	# Return an icon indicating the signal level.
	#
	#Parameters:
	#
	#	'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
	#
	#Returns:
	#
	#	gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
	def pixbuf_from_signal( self, signal ):
		signal = int( signal )
		# shift signal up by 80 to convert dBm scale to arbitrary scale
		if signal < 0: signal = signal + 80
		#print "signal level:", signal
		if signal < 3:
			return self.signal_none_pb
		elif signal < 12:
			return self.signal_low_pb
		elif signal < 20:
			return self.signal_barely_pb
		elif signal < 35:
			return self.signal_ok_pb
		elif signal >= 35:
			return self.signal_best_pb
		else:
			return None

	# Return row which holds specified ESSID and BSSID.
	#
	#Parameters:
	#
	#	'essid' -- string - ESSID to match
	#
	#	'bssid' -- string - BSSID to match
	#
	#Returns:
	#
	#	gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
	def get_row_by_ap( self, essid, bssid ):
		if bssid == "roaming":
			for row in self.pstore:
				if (row[0][:row[0].find("\n")] == essid):
					#print "roaming match:", row.iter, essid, bssid
					return row.iter
		else:
			for row in self.pstore:
				if (row[0] == essid + "\n" + bssid):
					#print "normal match:", row.iter, essid, bssid
					return row.iter
		return None

	# Enable/disable buttons based on the selected network.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def on_network_selection( self, widget, data = None ):
		( store, selected_iter ) = self.selected_network.get_selected()
		#print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
		# if no networks are selected, disable all buttons except New
		# (this occurs after a drag-and-drop)
		if selected_iter == None:
			self.edit_button.set_sensitive(False)
			self.delete_button.set_sensitive(False)
			self.connect_button.set_sensitive(False)
			return
		# enable/disable buttons
		self.connect_button.set_sensitive(True)
		if (store.get_value(selected_iter, 2) == True):				# is selected network known?
			self.edit_button.set_sensitive(True)
			self.delete_button.set_sensitive(True)
		else:
			self.edit_button.set_sensitive(True)
			self.delete_button.set_sensitive(False)

	# Init and run the about dialog
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def show_about_info( self, widget, data=None ):
		about = about_dialog()
		about.run()
		about.destroy()

	# Init and run the preferences dialog
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def edit_preferences( self, widget, data=None ):
		# get raw strings from config file
		self.confFile.raw = True
		prefs = preferences_dialog( self, self.confFile )
		response = prefs.run()
		if response == int(gtk.RESPONSE_ACCEPT):
			prefs.save()
		prefs.destroy()
		# get cooked strings from config file
		self.confFile.raw = False

	# Respond to a request to create a new AP profile
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'profile' -- dictionary - The AP profile to use as basis for new profile.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	boolean -- True if a profile was created and False if profile creation was canceled.
	def create_new_profile( self, widget, profile, data=None ):
		profile_editor = profile_dialog( self, profile )
		try:
			profile = profile_editor.run()
		except ValueError:
			error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
			del error_dlg
			return False
		finally:
			profile_editor.destroy()
		if profile:
			(store, selected_iter) = self.plist.get_selection().get_selected()
			if selected_iter is not None:
				store.remove(selected_iter)
			if profile['roaming']:
				apname = make_section_name(profile['essid'], '')
			else:
				apname = make_section_name(profile['essid'], profile['bssid'])
			# Check that the ap does not exist already
			if apname in self.confFile.profiles():
				error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
				del error_dlg
				# try again
			self.access_points[ apname ] = profile
			self.confFile.set_section( apname, profile )
			# if it is not in the auto_profile_order add it
			if apname not in self.confFile.auto_profile_order:
				self.confFile.auto_profile_order.insert(0, apname)
			# add to the store
			wep = None
			if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
			try:
				self.confFile.write()
			except IOError, (error_number, error_str):
				if error_number == errno.ENOENT:
					error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
					del error_dlg
				else:
					raise IOError(error_number, error_str)
			# Add AP to the list displayed to user
			if profile['roaming']:
				ap_name = profile['essid'] + "\n" + '  Multiple APs'
				while True:
					prow_iter = self.get_row_by_ap(profile['essid'], 'roaming')
					if prow_iter:
						self.pstore.remove(prow_iter)
					else:
						break
			else:
				ap_name = profile['essid'] + "\n" + profile['bssid']
			self.pstore.prepend([ap_name, self.pixbuf_from_known(profile['known']), profile['known'], profile['available'], wep, self.pixbuf_from_signal(profile['signal']), profile['mode'], profile['protocol'], profile['channel']])
			return True
		else:
			# Did not create new profile
			return False

	# Respond to a request to edit an AP profile.  Edit selected AP profile if it
	# is known.  Otherwise, create a new profile with the selected ESSID and BSSID.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def edit_profile(self, widget, data=None):
		(store, selected_iter) = self.plist.get_selection().get_selected()
		if not selected_iter:
			# No AP is selected
			return
		(essid, bssid) = str(self.pstore.get_value(selected_iter, 0)).split("\n")
		if bssid == '  Multiple APs':
			# AP list says this is a roaming profile
			apname = make_section_name(essid, '')
		else:
			# AP list says this is NOT a roaming profile
			apname = make_section_name(essid, bssid)
		profile = self.confFile.get_profile(apname)
		if profile:
			# A profile was found in the config file
			profile['bssid'] = self.access_points[apname]['bssid']
			profile_editor = profile_dialog(self, profile)
			try:
				# try editing the profile
				edited_profile = profile_editor.run()
			except ValueError:
				error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
				del error_dlg
				return False
			finally:
				# Always remove profile editor window from screen
				profile_editor.destroy()
			if edited_profile:
				# A profile was returned by the editor
				old_index = None
				row = None
				if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
					# ESSID, BSSID, or roaming was changed in profile editor
					try:
						self.commandQueue.put('pause')
						self.commandQueue.join()
					except Queue.Full:
						pass
					if profile['roaming']:
						# The old profile was a roaming profile
						old_ap = make_section_name(profile['essid'], '')
					else:
						# The old profile was NOT a roaming profile
						old_ap = make_section_name(profile['essid'], profile['bssid'])
					# Find where old profile was in auto order
					old_index = self.confFile.auto_profile_order.index(old_ap)
					# Remove old profile and get its place in AP list
					row = self.delete_profile(selected_iter, old_ap)
					self.confFile.remove_section(old_ap)
					try:
						# Add AP to the list displayed to user
						self.apQueue.put_nowait(edited_profile)
						self.commandQueue.put('scan')
					except Queue.Full:
						pass
				if edited_profile['roaming']:
					# New profile is a roaming profile
					apname = make_section_name(edited_profile['essid'], '')
					ap_display = edited_profile['essid'] + "\n" + '  Multiple APs'
					# Remove all other profiles that match the new profile ESSID
					while True:
						prow_iter = self.get_row_by_ap(edited_profile['essid'], 'roaming')
						if prow_iter:
							self.pstore.remove(prow_iter)
						else:
							break
				else:
					# New profile is NOT a roaming profile
					apname = make_section_name(edited_profile['essid'], edited_profile['bssid'])
					ap_display = edited_profile['essid'] + "\n" + edited_profile['bssid']
				# Insert the new profile in the same position as the one being replaced
				if old_index != None:
					# Old profile was in auto order list
					self.confFile.auto_profile_order.insert(old_index, apname)
				if ((row is not None) and (self.pstore.iter_is_valid(row))):
					self.pstore.insert_before(row, [ap_display, None, None, None, None, None, None, None, None])
				self.access_points[apname] = edited_profile
				self.confFile.set_section(apname, edited_profile)
				try:
					# Save updated profile to config file
					self.confFile.write()
				except IOError, (error_number, error_str):
					if error_number == errno.ENOENT:
						error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
						del error_dlg
					else:
						raise IOError(error_number, error_str)
		else:
			# The AP does not already have a profile
			profile = get_new_profile()
			( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
			self.create_new_profile( widget, profile, data )

	# Delete an AP profile (i.e. make profile unknown)
	#
	#Parameters:
	#
	#	'selected_iter' -- gtk.TreeIter - The selected row.
	#
	#	'apname' -- string - The configuration file section to remove
	#
	#Returns:
	#
	#	gtk.TreeIter -- the iter for the row removed from the gtk.ListStore
	def delete_profile(self, selected_iter, apname):
		# Remove it
		del self.access_points[apname]
		self.confFile.remove_section(apname)
		self.logger.info(apname)
		if apname in self.confFile.auto_profile_order:
			self.confFile.auto_profile_order.remove(apname)
		self.pstore.remove(selected_iter)
		# Let's save our current state
		self.update_auto_profile_order()
		try:
			self.confFile.write()
		except IOError, (error_number, error_str):
			if error_number == errno.ENOENT:
				error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
				del error_dlg
			else:
				raise IOError(error_number, error_str)
		return selected_iter

	# Respond to a request to delete an AP profile (i.e. make profile unknown)
	# Check with user first.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def delete_profile_with_check(self, widget, data=None):
		(store, selected_iter) = self.plist.get_selection().get_selected()
		if not selected_iter: return
		(essid, bssid) = store.get_value(selected_iter, 0).split("\n")
		if bssid == '  Multiple APs':
			apname = make_section_name(essid, '')
		else:
			apname = make_section_name(essid, bssid)
		profile = self.confFile.get_profile(apname)
		if profile['roaming']:
			dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s profile?" % (essid, ))
		else:
			dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid))
		known = store.get_value( selected_iter, 1 )
		if not known: return
		res = dlg.run()
		dlg.destroy()
		del dlg
		if res == gtk.RESPONSE_NO:
			return
		self.delete_profile(selected_iter, apname)
	
	# Respond to a request to connect to an AP.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'profile' -- dictionary - The AP profile to which to connect.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def connect_profile( self, widget, profile, data=None ):
		( store, selected_iter ) = self.plist.get_selection().get_selected()
		if not selected_iter: return
		( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
		known	= store.get_value( selected_iter, 2 )
		if not known:
			dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "This network does not have a profile configured.\n\nWould you like to create one now?")
			res = dlg.run()
			dlg.destroy()
			del dlg
			if res == gtk.RESPONSE_NO:
				return
			profile = get_new_profile()
			profile['essid'] = essid
			profile['bssid'] = bssid
			if not self.create_new_profile( widget, profile, data ):
				return
		else:
			# Check for roaming profile.
			ap_name = make_section_name(essid, '')
			profile = self.confFile.get_profile(ap_name)
			if not profile:
				# Check for normal profile.
				ap_name = make_section_name(essid, bssid)
				profile = self.confFile.get_profile(ap_name)
				if not profile:
					# No configured profile
					return
			profile['bssid'] = self.access_points[ap_name]['bssid']
			profile['channel'] = self.access_points[ap_name]['channel']
		self.connection.connect_to_network(profile, self.status_window)

	# Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def disconnect_profile( self, widget, data=None ):
		if data == "cancel":
			self.status_window.update_message("Canceling connection...")
			if sys.modules.has_key("gtk"):
				while gtk.events_pending():
					gtk.main_iteration(False)
			sleep(1)
		self.connection.disconnect_interface()
	
	# Update the config file auto profile order from the on-screen order
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#	'data2' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
		# recreate the auto_profile_order
		auto_profile_order = []
		piter = self.pstore.get_iter_first()
		while piter:
			# only if it's known
			if self.pstore.get_value(piter, 2) == True:
				(essid, bssid) = self.pstore.get_value(piter, 0).split("\n")
				if bssid == '  Multiple APs':
					apname = make_section_name(essid, '')
				else:
					apname = make_section_name(essid, bssid)
				auto_profile_order.append(apname)
			piter = self.pstore.iter_next(piter)
		self.confFile.auto_profile_order = auto_profile_order
		try:
			self.confFile.write()
		except IOError, (error_number, error_str):
			if error_number == errno.ENOENT:
				error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
				del error_dlg
			else:
				raise IOError(error_number, error_str)


# Button to allow user to choose a file and put value into specified gtk.Entry
class file_browse_button(gtk.Button):
	# Create a button to simulate a File/Open
	#
	#Parameters:
	#
	#	'parent' -- gtk.Object -- Usually, the calling window.
	#
	#	'entry' -- gtk.Entry -- The text entry to update with user selection.
	#
	#Returns:
	#
	#	file_browse_button instance
	def __init__( self, parent, entry ):
		self.parent_window = parent
		self.entry = entry
		gtk.Button.__init__(self, "Browse", None)
		#self.
		self.browser_dialog = gtk.FileChooserDialog(None, self.parent_window, gtk.FILE_CHOOSER_ACTION_OPEN, ( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK ), None)
		self.connect("clicked", self.browse_files)
	
	# Show filechooser dialog and get user selection
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget -- The widget sending the event.
	#
	#Returns:
	#
	#	nothing
	#
	#NOTES:
	#
	#	updates entry value
	#
	def browse_files( self, widget ):
		self.browser_dialog.set_filename(self.entry.get_text())
		self.browser_dialog.run()
		filename = self.browser_dialog.get_filename()
		if filename:
			self.entry.set_text(filename)
		self.browser_dialog.destroy()


# Simple dialog to report an error to the user.
class ErrorDialog:
	# Create a new ErrorDialog.
	#
	#Parameters:
	#
	#	'parent' -- gtk.Object - Usually, the calling window.
	#
	#	'message' -- string - The message to display to the user.
	#
	#Returns:
	#
	#	ErrorDialog instance
	def __init__( self, parent, message ):
		dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
		dialog.run()
		dialog.destroy()
		del dialog


# The preferences dialog.  Edits non-profile sections of the config file.
class preferences_dialog:
	# Create a new preferences_dialog.
	#
	#Parameters:
	#
	#	'parent' -- gtk.Object - Usually, the calling window.
	#
	#	'confFile' -- ConfigFile - The config file in which to store/read settings.
	#
	#Returns:
	#
	#	preferences_dialog instance
	def __init__( self, parent, confFile ):
		global wifi_radar_icon
		self.parent = parent
		self.confFile = confFile
		self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
			gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 
			( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
		icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
		self.dialog.set_icon( icon )
		self.dialog.set_resizable( True )
		self.dialog.set_transient_for( self.parent.window )
		self.tooltips = gtk.Tooltips()
		#
		# set up preferences widgets
		#
		# build everything in a tabbed notebook
		self.prefs_notebook = gtk.Notebook()
		
		### General tab
		self.general_page = gtk.VBox()
		# auto detect wireless device
		self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
		
		self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
		self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
		self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
		self.general_page.pack_start(self.w_auto_detect, False, False, 5)
		
		# network interface selecter
		self.w_interface = gtk.combo_box_entry_new_text()
		try:
			iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE, stderr=STDOUT).stdout
		except OSError, (errno, strerror):
			if errno == 2:
				logger.critical("iwconfig command not found, please set this in the preferences.")
				iwconfig_info = ""
		wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
		for device in wireless_devices:
			if device != self.confFile.get_opt('DEFAULT.interface'):
				self.w_interface.append_text(device)
		if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
			self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
		self.w_interface.set_active(0)
		self.w_interface_label = gtk.Label("Wireless device")
		self.w_hbox1 = gtk.HBox(False, 0)
		self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
		self.w_hbox1.pack_start(self.w_interface, True, True, 0)
		self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
		self.general_page.pack_start(self.w_hbox1, False, False, 5)
		
		# scan timeout (spin button of integers from 1 to 100)
		#self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
		#self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
		#self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
		#self.w_scan_timeout.set_numeric(True)
		#self.w_scan_timeout.set_snap_to_ticks(True)
		#self.w_scan_timeout.set_wrap(False)
		#self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
		#self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
		#self.w_hbox2 = gtk.HBox(False, 0)
		#self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
		#self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
		#self.general_page.pack_start(self.w_hbox2, False, False, 5)
		
		# speak up
		self.w_speak_up = gtk.CheckButton("Use speak-up")
		self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
		self.w_speak_up.connect("toggled", self.toggle_speak)
		self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
		self.general_page.pack_start(self.w_speak_up, False, False, 5)
		
		# speak up command
		self.w_speak_cmd = gtk.Entry()
		self.w_speak_cmd.set_width_chars(16)
		self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
		self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
		self.w_speak_cmd_label = gtk.Label("Speak Command")
		self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
		self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
		self.w_hbox3 = gtk.HBox(False, 0)
		self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
		self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
		self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
		self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
		self.general_page.pack_start(self.w_hbox3, False, False, 5)
		
		# commit required
		self.w_commit_required = gtk.CheckButton("Commit required")
		self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
		self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
		self.general_page.pack_start(self.w_commit_required, False, False, 5)
		
		# ifup required
		self.w_ifup_required = gtk.CheckButton("Ifup required")
		self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
		self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
		self.general_page.pack_start(self.w_ifup_required, False, False, 5)
		
		self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
		### End of General tab
		
		### Advanced tab
		# table to use for layout of following command configurations
		self.cmds_table = gtk.Table()
		
		# ifconfig command
		self.ifconfig_cmd = gtk.Entry()
		self.ifconfig_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
		self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
		self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
		self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
		#                     (widget,                   l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.ifconfig_cmd_label,  1, 2, 1, 2, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.ifconfig_cmd,        2, 3, 1, 2, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		# iwconfig command
		self.iwconfig_cmd = gtk.Entry()
		self.iwconfig_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
		self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
		self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
		self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
		#                     (widget,                   l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.iwconfig_cmd_label,  1, 2, 2, 3, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.iwconfig_cmd,        2, 3, 2, 3, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		# iwlist command
		self.iwlist_cmd = gtk.Entry()
		self.iwlist_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
		self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
		self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
		self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
		#                     (widget,                 l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.iwlist_cmd_label,  1, 2, 3, 4, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.iwlist_cmd,        2, 3, 3, 4, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		# route command
		self.route_cmd = gtk.Entry()
		self.route_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
		self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
		self.route_cmd_label = gtk.Label("Network route configure command")
		self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
		#                     (widget,                l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.route_cmd_label,  1, 2, 4, 5, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.route_cmd,        2, 3, 4, 5, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		# log file
		self.logfile_entry = gtk.Entry()
		self.logfile_entry.set_width_chars(32)
		self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
		self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
		self.logfile_label = gtk.Label("Log file")
		self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
		#                     (widget,              l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.logfile_label,  1, 2, 5, 6, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.logfile_entry,  2, 3, 5, 6, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		# log level (spin button of integers from 0 to 50 by 5's)
		self.loglevel = gtk.SpinButton(gtk.Adjustment(self.confFile.get_opt_as_int('DEFAULT.loglevel'), 1, 50, 5, 5, 0), 1, 0)
		self.loglevel.set_update_policy(gtk.UPDATE_IF_VALID)
		self.loglevel.set_numeric(True)
		self.loglevel.set_snap_to_ticks(True)
		self.loglevel.set_wrap(False)
		self.tooltips.set_tip(self.loglevel, "How much detail to save in log file.  Larger numbers provide less detail and smaller numbers, more detail.")
		self.loglevel.set_text(self.confFile.get_opt('DEFAULT.loglevel'))
		self.loglevel_label = gtk.Label("Log level")
		#                     (widget,              l, r, t, b, xopt,                yopt, xpad, ypad)
		self.cmds_table.attach(self.loglevel_label, 1, 2, 6, 7, gtk.FILL|gtk.EXPAND, 0,    5,    0)
		self.cmds_table.attach(self.loglevel,       2, 3, 6, 7, gtk.FILL|gtk.EXPAND, 0,    0,    0)
		
		self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
		### End of Advanced tab
		
		### DHCP tab
		# table to use for layout of DHCP prefs
		self.dhcp_table = gtk.Table()
		
		self.dhcp_cmd = gtk.Entry()
		self.dhcp_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
		self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
		self.dhcp_cmd_label = gtk.Label("Command")
		self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
		self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
		self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
		self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
		
		self.dhcp_args = gtk.Entry()
		self.dhcp_args.set_width_chars(32)
		self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
		self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
		self.dhcp_args_label = gtk.Label("Arguments")
		self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
		self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
		
		self.dhcp_kill_args = gtk.Entry()
		self.dhcp_kill_args.set_width_chars(32)
		self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
		self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
		self.dhcp_kill_args_label = gtk.Label("Kill arguments")
		self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
		self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
		
		self.dhcp_timeout = gtk.Entry()
		self.dhcp_timeout.set_width_chars(32)
		self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
		self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
		self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
		self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
		self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
		
		self.dhcp_pidfile = gtk.Entry()
		self.dhcp_pidfile.set_width_chars(32)
		self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
		self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
		self.dhcp_pidfile_label = gtk.Label("PID file")
		self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
		self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
		
		self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
		### End of DHCP tab
		
		### WPA tab
		# table to use for layout of DHCP prefs
		self.wpa_table = gtk.Table()
		
		self.wpa_cmd = gtk.Entry()
		self.wpa_cmd.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
		self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
		self.wpa_cmd_label = gtk.Label("Command")
		self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
		self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
		self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
		
		self.wpa_args = gtk.Entry()
		self.wpa_args.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
		self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
		self.wpa_args_label = gtk.Label("Arguments")
		self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
		
		self.wpa_kill_args = gtk.Entry()
		self.wpa_kill_args.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
		self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
		self.wpa_kill_args_label = gtk.Label("Kill command")
		self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
		
		self.wpa_config = gtk.Entry()
		self.wpa_config.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
		self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
		self.wpa_config_label = gtk.Label("Configuration file")
		self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
		
		self.wpa_driver = gtk.Entry()
		self.wpa_driver.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
		self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
		self.wpa_driver_label = gtk.Label("Driver")
		self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
		
		self.wpa_pidfile = gtk.Entry()
		self.wpa_pidfile.set_width_chars(32)
		self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
		self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
		self.wpa_pidfile_label = gtk.Label("PID file")
		self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
		self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
		
		self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
		### End of WPA tab
		
		self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
	
	# Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
	#
	#Parameters:
	#
	#	'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def toggle_auto_detect(self, auto_detect_toggle, data=None):
		self.w_interface.set_sensitive(not auto_detect_toggle.get_active())

	# Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
	#
	#Parameters:
	#
	#	'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def toggle_speak(self, speak_toggle, data=None):
		self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
		self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())

	# Display preferences dialog and operate until canceled or okayed.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	integer -- gtk response ID
	def run(self):
		self.dialog.show_all()
		return self.dialog.run()

	# Write updated values to config file.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def save(self):
		if self.w_auto_detect.get_active():
			self.confFile.set_opt('DEFAULT.interface', "auto_detect")
		else:
			interface = "auto_detect"
			if self.w_interface.get_active_text() != "":
				interface = self.w_interface.get_active_text()
			self.confFile.set_opt('DEFAULT.interface', interface)
		#self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
		self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
		self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
		self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
		self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
		self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
		self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
		self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
		self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
		self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
		self.confFile.set_int_opt('DEFAULT.loglevel', int(self.loglevel.get_value()))
		self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
		self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
		self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
		self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
		self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
		self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
		self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
		self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
		self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
		self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
		self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
		try:
			self.confFile.write()
		except IOError, (error_number, error_str):
			if error_number == errno.ENOENT:
				error_dlg = ErrorDialog( self.dialog, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
				del error_dlg
			else:
				raise IOError(error_number, error_str)

    # Remove preferences window.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def destroy(self):
		self.dialog.destroy()
		del self.dialog


# Edit and return an AP profile.
class profile_dialog:
	# Create a new profile_dialog.
	#
	#Parameters:
	#
	#	'parent' -- gtk.Object - Usually, the calling window.
	#
	#	'profile' -- dictionary - The profile to edit.  May be mostly-empty default profile.
	#
	#Returns:
	#
	#	profile_dialog instance
	def __init__( self, parent, profile ):
		global wifi_radar_icon
		
		# Labels
		self.WIFI_SET_LABEL	= "WiFi Options"
		self.USE_DHCP_LABEL	= "Automatic network configuration (DHCP)"
		self.USE_IP_LABEL	= "Manual network configuration"
		self.USE_WPA_LABEL	= "Use WPA"
		self.NO_WPA_LABEL	= "No WPA"
		self.CON_PP_LABEL	= "Connection Commands"
		self.DIS_PP_LABEL	= "Disconnection Commands"
		
		self.parent = parent
		self.profile = profile.copy()
		self.WIFI_MODES		= [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
		self.WIFI_SECURITY	= [ '', 'open', 'restricted' ]
		self.WIFI_CHANNELS	= [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
		self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
								gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
								( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
		icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
		self.dialog.set_icon( icon )
		self.dialog.set_resizable( False )
		self.dialog.set_transient_for( self.parent.window )
		#self.dialog.set_size_request( 400, 400 )
		#################
		self.tooltips = gtk.Tooltips()

		general_table = gtk.Table()
		general_table.set_row_spacings(3)
		general_table.set_col_spacings(3)
		# The essid labels
		general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
		# The essid textboxes
		self.essid_entry = gtk.Entry(32)
		self.essid_entry.set_text(self.profile['essid'])
		general_table.attach(self.essid_entry, 1, 2, 0, 1)
		# Add the essid table to the dialog
		self.dialog.vbox.pack_start(general_table, True, True, 5)
		self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")

		# The bssid labels
		general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
		# The bssid textboxes
		self.bssid_entry = gtk.Entry(32)
		self.bssid_entry.set_text(self.profile['bssid'])
		self.bssid_entry.set_sensitive(not self.profile['roaming'])
		# Add the bssid table to the dialog
		general_table.attach(self.bssid_entry, 1, 2, 1, 2)
		self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
		# Add the roaming checkbox
		self.roaming_cb = gtk.CheckButton('Roaming')
		self.roaming_cb.set_active(self.profile['roaming'])
		self.roaming_cb.connect("toggled", self.toggle_roaming)
		general_table.attach(self.roaming_cb, 1, 2, 2, 3)
		self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
		# create the WiFi expander
		self.wifi_expander = gtk.Expander( self.WIFI_SET_LABEL )
		#self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
		wifi_table = gtk.Table( 4, 2, False )
		wifi_table.set_row_spacings( 3 )
		wifi_table.set_col_spacings( 3 )
		# The WiFi labels
		wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
		wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
		wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
		wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
		# The WiFi text boxes
		self.mode_combo = gtk.combo_box_new_text()
		for mode in self.WIFI_MODES:
			self.mode_combo.append_text( mode )
		self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
		wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
		self.tooltips.set_tip(self.mode_combo, "Method to use for connection.  You probably want auto mode.")
		self.channel_combo = gtk.combo_box_new_text()
		for channel in self.WIFI_CHANNELS:
			self.channel_combo.append_text( channel )
		self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
		wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
		self.tooltips.set_tip(self.channel_combo, "Channel the network uses.  You probably want auto mode.")
		
		self.key_entry = gtk.Entry( 64 )
		self.key_entry.set_text( self.profile['key'] )
		wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
		self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
		
		self.security_combo = gtk.combo_box_new_text()
		for security in self.WIFI_SECURITY:
			self.security_combo.append_text( security )
		self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
		wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
		self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
		# Add the wifi table to the expander
		self.wifi_expander.add( wifi_table )
		# Add the expander to the dialog
		self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )

		# create the wpa expander
		self.wpa_expander = gtk.Expander( self.NO_WPA_LABEL )
		self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
		wpa_table = gtk.Table( 1, 2, False )
		wpa_table.set_row_spacings( 3 )
		wpa_table.set_col_spacings( 3 )
		# The labels
		wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
		# The text boxes
		self.wpa_driver_entry = gtk.Entry()
		self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
		wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
		# Add the wpa table to the expander
		self.wpa_expander.add( wpa_table )
		self.wpa_expander.set_expanded(self.profile['use_wpa'])
		# Add the expander to the dialog
		self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )

		# create the dhcp expander
		self.dhcp_expander = gtk.Expander( self.USE_DHCP_LABEL )
		self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
		self.dhcp_expander.set_expanded(not self.profile['use_dhcp'])
		ip_table = gtk.Table( 6, 2, False )
		ip_table.set_row_spacings( 3 )
		ip_table.set_col_spacings( 3 )
		# The IP labels
		ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
		ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
		ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
		ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
		ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
		ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
		# The IP text boxes
		self.ip_entry = gtk.Entry( 15 )
		self.ip_entry.set_text( self.profile['ip'] )
		ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
		self.netmask_entry = gtk.Entry( 15 )
		self.netmask_entry.set_text( self.profile['netmask'] )
		ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
		self.gw_entry = gtk.Entry( 15 )
		self.gw_entry.set_text( self.profile['gateway'] )
		ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
		self.domain_entry = gtk.Entry( 32 )
		self.domain_entry.set_text( self.profile['domain'] )
		ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
		self.dns1_entry = gtk.Entry( 15 )
		self.dns1_entry.set_text( self.profile['dns1'] )
		ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
		self.dns2_entry = gtk.Entry( 15 )
		self.dns2_entry.set_text( self.profile['dns2'] )
		ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
		# Add the ip table to the expander
		self.dhcp_expander.add( ip_table )
		# Add the expander to the dialog
		self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )

		# create the connection-building postpre expander
		self.con_pp_expander = gtk.Expander( self.CON_PP_LABEL )
		con_pp_table = gtk.Table( 2, 2, False )
		con_pp_table.set_row_spacings( 3 )
		con_pp_table.set_col_spacings( 3 )
		# The labels
		con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
		con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
		# The text boxes
		self.con_prescript_entry = gtk.Entry()
		self.con_prescript_entry.set_text( self.profile['con_prescript'] )
		con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
		self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
		self.con_postscript_entry = gtk.Entry()
		self.con_postscript_entry.set_text( self.profile['con_postscript'] )
		con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
		self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
		# Add the pp table to the expander
		self.con_pp_expander.add( con_pp_table )
		# Add the expander to the dialog
		self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )

		# create the disconnection postpre expander
		self.dis_pp_expander = gtk.Expander( self.DIS_PP_LABEL )
		dis_pp_table = gtk.Table( 2, 2, False )
		dis_pp_table.set_row_spacings( 3 )
		dis_pp_table.set_col_spacings( 3 )
		# The labels
		dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
		dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
		# The text boxes
		self.dis_prescript_entry = gtk.Entry()
		self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
		dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
		self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
		self.dis_postscript_entry = gtk.Entry()
		self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
		dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
		self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
		# Add the pp table to the expander
		self.dis_pp_expander.add( dis_pp_table )
		# Add the expander to the dialog
		self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )

	# Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	dictionary or None -- a profile, or None on cancel
	#
	#NOTES:
	#
	#	Raises ValueError if an attempt is made to save an ESSID with no name.
	def run( self ):
		self.dialog.show_all()
		if self.dialog.run():
			if self.essid_entry.get_text().strip() == "":
				raise ValueError
			self.profile['known'] 			= True
			self.profile['essid']			= self.essid_entry.get_text().strip()
			if self.roaming_cb.get_active():
				self.profile['bssid']		= ''
			else:
				self.profile['bssid']		= self.bssid_entry.get_text().strip()
			self.profile['roaming']			= self.roaming_cb.get_active()
			self.profile['key']				= self.key_entry.get_text().strip()
			self.profile['mode']			= self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
			self.profile['security']		= self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
			self.profile['encrypted'] 		= ( self.profile['security'] != '' )
			self.profile['channel']			= self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
			self.profile['protocol']		= 'g'
			self.profile['available']		= ( self.profile['signal'] > 0 )
			self.profile['con_prescript']	= self.con_prescript_entry.get_text().strip()
			self.profile['con_postscript']	= self.con_postscript_entry.get_text().strip()
			self.profile['dis_prescript']	= self.dis_prescript_entry.get_text().strip()
			self.profile['dis_postscript']	= self.dis_postscript_entry.get_text().strip()
			# wpa
			self.profile['use_wpa'] 		= self.wpa_expander.get_expanded()
			self.profile['wpa_driver']		= self.wpa_driver_entry.get_text().strip()
			# dhcp
			self.profile['use_dhcp']		= not self.dhcp_expander.get_expanded()
			self.profile['ip']				= self.ip_entry.get_text().strip()
			self.profile['netmask']			= self.netmask_entry.get_text().strip()
			self.profile['gateway']			= self.gw_entry.get_text().strip()
			self.profile['domain']			= self.domain_entry.get_text().strip()
			self.profile['dns1']			= self.dns1_entry.get_text().strip()
			self.profile['dns2']			= self.dns2_entry.get_text().strip()
			return self.profile
		return None

	# Remove profile dialog.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def destroy( self ):
		self.dialog.destroy()
		del self.dialog

	# Respond to roaming checkbox toggle by activating/de-activating the BSSID text entry.
	#
	#Parameters:
	#
	#	'roaming_toggle' -- gtk.CheckButton - The checkbox sending the signal.
	#
	#	'data' -- tuple - list of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def toggle_roaming(self, roaming_toggle, data=None):
		self.bssid_entry.set_sensitive(not roaming_toggle.get_active())

	# Respond to expanding/hiding IP segment.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - List of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def toggle_use_dhcp( self, widget, data = None ):
		expanded = self.dhcp_expander.get_expanded()
		if expanded:
			self.dhcp_expander.set_label( self.USE_IP_LABEL )
		else:
			self.dhcp_expander.set_label( self.USE_DHCP_LABEL )

	# Respond to expanding/hiding WPA segment.
	#
	#Parameters:
	#
	#	'widget' -- gtk.Widget - The widget sending the event.
	#
	#	'data' -- tuple - List of arbitrary arguments (not used)
	#
	#Returns:
	#
	#	nothing
	def toggle_use_wpa( self, widget, data = None ):
		expanded = self.wpa_expander.get_expanded()
		if expanded:
			self.wpa_expander.set_label( self.USE_WPA_LABEL )
		else:
			self.wpa_expander.set_label( self.NO_WPA_LABEL )

	# Return the index where item matches a cell in array.
	#
	#Parameters:
	#
	#	'item' -- string - Item to find in array
	#
	#	'array' -- list - List in which to find match.
	#
	#Returns:
	#
	#	integer - 0 (no match) or higher (index of match)
	def get_array_index( self, item, array ):
		try:
			return array.index( item.strip() )
		except:
			pass
		return 0

	# Return the value in array[ index ]
	#
	#Parameters:
	#
	#	'index' -- integer - The index to look up.
	#
	#	'array' -- list - List in which to look up value.
	#
	#Returns:
	#
	#	string -- empty string (no match) or looked up value
	def get_array_item( self, index, array ):
		try:
			return array[ index ]
		except:
			pass
		return ''


# A simple class for putting up a "Please wait" dialog so the user
# doesn't think we've forgotten about them.  Implements the status interface.
class StatusWindow:
	# Create a new StatusWindow.
	#
	#Parameters:
	#
	#	'parent' -- gtk.Object - Usually, the calling window.
	#
	#Returns:
	#
	#	StatusWindow instance
	#
	#NOTE:
	#
	#	Sample implementation of status interface.  Status interface
	#requires .show(), .update_message(message), and .hide() methods.
	def __init__( self, parent ):
		global wifi_radar_icon
		self.parent = parent
		self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
		icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
		self.dialog.set_icon( icon )
		self.lbl = gtk.Label("Please wait...")
		self.bar = gtk.ProgressBar()
		self.dialog.vbox.pack_start(self.lbl)
		self.dialog.vbox.pack_start(self.bar)
		self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
		self.timer = None
	
	# Change the message displayed to the user.
	#
	#Parameters:
	#
	#	'message' -- string - The message to show to the user.
	#
	#Returns:
	#
	#	nothing
	def update_message( self, message ):
		self.lbl.set_text(message)
	
	# Update the StatusWindow progress bar.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	True -- always return True
	def update_window( self ):
		self.bar.pulse()
		return True
	
	# Display and operate the StatusWindow.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def run( self ):
		pass
	
	# Show all the widgets of the StatusWindow.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def show( self ):
		self.dialog.show_all()
		self.timer = gobject.timeout_add(250, self.update_window)
		return False
	
	# Hide all the widgets of the StatusWindow.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def hide( self ):
		if self.timer:
			gobject.source_remove(self.timer)
		self.timer = None
		self.dialog.hide_all()
		return False
	
	# Remove the StatusWindow.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def destroy( self ):
		if self.timer:
			gobject.source_remove(self.timer)
		self.dialog.destroy()
		del self.dialog


# Manage a GTK About Dialog
class about_dialog(gtk.AboutDialog):
	# Subclass GTK AboutDialog
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def __init__( self ):
		global	wifi_radar_icon

		gtk.AboutDialog.__init__(self)
		self.set_authors(["Ahmad Baitalmal <ahmad@baitalmal.com>", "Brian Elliott Finley <brian@thefinleys.com>", "Sean Robinson <seankrobinson@gmail.com>", "", "Contributors", "Douglas Breault", "Jon Collette", "David Decotigny", "Simon Gerber", "Joey Hurst", "Ante Karamatic", "Richard Monk", "Nicolas Brouard", "Kevin Otte", "Nathanael Rebsch", "Andrea Scarpino", "Prokhor Shuchalov", "Patrick Winnertz"])
		self.set_comments("WiFi connection manager")
		self.set_copyright("Copyright 2004-2010 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
		self.set_documenters(["Gary Case"])
		license = """
            This program is free software; you can redistribute it and/or modify
            it under the terms of the GNU General Public License as published by
            the Free Software Foundation; either version 2 of the License, or
            (at your option) any later version.

            This program is distributed in the hope that it will be useful,
            but WITHOUT ANY WARRANTY; without even the implied warranty of
            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            GNU General Public License for more details.

            You should have received a copy of the GNU General Public License
            along with this program; if not, write to the Free Software
            Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA"""
		self.set_license(license)
		logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
		self.set_logo(logo)
		self.set_name("WiFi Radar")
		self.set_version(WIFI_RADAR_VERSION)
		self.set_website("http://wifi-radar.berlios.de")



# Manage the configuration for the application, including reading and writing the config from/to a file.
class ConfigFile(ConfigParser.SafeConfigParser):
	# Create a new ConfigFile.
	#
	#Parameters:
	#
	#	'filename' -- string - The configuration file's name.
	#
	#	'defaults' -- dictionary - Default values for the DEFAULT section.
	#
	#Returns:
	#
	#	ConfigFile instance
	def __init__( self, filename, defaults, raw=False ):
		self.filename = filename
		self.raw = raw
		self.auto_profile_order = []
		ConfigParser.SafeConfigParser.__init__(self, defaults)
	
	# Set the contents of a section to values from a dictionary.
	#
	#Parameters:
	#
	#	'section_name' -- string - Configuration file section.
	#
	#	'section_dict' -- dictionary - Values to add to section.
	#
	#Returns:
	#
	#	nothing
	def set_section( self, section_name, section_dict ):
		try:
			self.add_section(section_name)
		except ConfigParser.DuplicateSectionError:
			pass
		for key in section_dict.keys():
			if type(section_dict[key]) == BooleanType:
				self.set_bool_opt(section_name + "." + key, section_dict[key])
			elif type(section_dict[key]) == IntType:
				self.set_int_opt(section_name + "." + key, section_dict[key])
			elif type(section_dict[key]) == FloatType:
				self.set_float_opt(section_name + "." + key, section_dict[key])
			else:
				self.set_opt(section_name + "." + key, section_dict[key])
	
	# Return the profile recorded in the specified section.
	#
	#Parameters:
	#
	#	'section_name' -- string - Configuration file section.
	#
	#Returns:
	#
	#	dictionary or None - The specified profile or None if not found
	def get_profile( self, section_name ):
		if section_name in self.profiles():
			str_types = [ 'bssid', 'channel', 'essid', 'protocol', 'con_prescript', 'con_postscript', 'dis_prescript', 'dis_postscript', 'key', 'mode', 'security', 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain', 'dns1', 'dns2' ]
			bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
			int_types = [ 'signal' ]
			profile = get_new_profile()
			for option in bool_types:
				profile[option] = self.get_opt_as_bool(section_name + "." + option)
			for option in int_types:
				option_tmp = self.get_opt_as_int(section_name + "." + option)
				if option_tmp:
					profile[option] = option_tmp
			for option in str_types:
				option_tmp = self.get_opt(section_name + "." + option)
				if option_tmp:
					profile[option] = option_tmp
			return profile
		return None
	
	# Get a config option and handle exceptions.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#Returns:
	#
	#	string or None - option value as string or None on failure
	def get_opt( self, option_path ):
		#print "ConfigFile.get_opt: ", option_path
		(section, option) = option_path.split('.')
		try:
			return self.get(section, option, self.raw)
		except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
			return None
	
	# Get a config option and return as a boolean type.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#Returns:
	#
	#	boolean - option value as boolean
	def get_opt_as_bool( self, option_path ):
		option = self.get_opt(option_path)
		if isinstance(option, BooleanType) or isinstance(option, NoneType):
			return option
		if option == 'True':
			return True
		if option == 'False':
			return False
		raise ValueError, 'boolean option was not True or False'
	
	# Get a config option and return as an integer type.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#Returns:
	#
	#	integer- option value as integer
	def get_opt_as_int( self, option_path ):
		return int(float(self.get_opt(option_path)))
	
	# Convert boolean type to string and set config option.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#	'value' -- boolean - Value to set.
	#
	#Returns:
	#
	#	nothing
	def set_bool_opt( self, option_path, value ):
		if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
			value == 'True'
		elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
			value == 'False'
		else:
			raise ValueError, 'cannot convert value to string'
		self.set_opt(option_path, repr(value))
	
	# Convert integer type to string and set config option.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#	'value' -- integer - Value to set.
	#
	#Returns:
	#
	#	nothing
	def set_int_opt( self, option_path, value ):
		if not isinstance(value, IntType):
			raise ValueError, 'value is not an integer'
		self.set_opt(option_path, repr(value))
	
	# Convert float type to string and set config option.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#	'value' -- float - Value to set.
	#
	#Returns:
	#
	#	nothing
	def set_float_opt( self, option_path, value ):
		if not isinstance(value, FloatType):
			raise ValueError, 'value is not a float'
		self.set_opt(option_path, repr(int(value)))
	
	# Set a config option while handling exceptions.
	#
	#Parameters:
	#
	#	'option_path' -- string - Section (a.k.a. profile) name concatenated with a
	#								period and the option key. (E.g. "DEFAULT.interface")
	#
	#	'value' -- string - Value to set.
	#
	#Returns:
	#
	#	nothing
	def set_opt( self, option_path, value ):
		(section, option) = option_path.split('.')
		try:
			self.set(section, option, value)
		except ConfigParser.NoSectionError:
			self.add_section(section)
			self.set_opt(option_path, value)
	
	# Return a list of the section names which denote AP profiles.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	list - profile names
	def profiles( self ):
		profile_list = []
		for section in self.sections():
			if ':' in section:
				profile_list.append(section)
		return profile_list
	
	# Read configuration file from disk into instance variables.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def read( self ):
		fp = open( self.filename, "r" )
		self.readfp(fp)
		# convert the auto_profile_order to a list for ordering
		self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
		for ap in self.profiles():
			self.set_bool_opt( ap + '.known', True)
			if ap in self.auto_profile_order: continue
			self.auto_profile_order.append( ap )
		fp.close()
		# Remove any auto_profile_order AP without a matching section.
		auto_profile_order_copy = self.auto_profile_order[:]
		for ap in auto_profile_order_copy:
			if ap not in self.profiles():
				self.auto_profile_order.remove(ap)
	
	# Write configuration file to disk from instance variables.  Copied from
	# ConfigParser and modified to write options in alphabetical order.
	#
	#Parameters:
	#
	#	nothing
	#
	#Returns:
	#
	#	nothing
	def write( self ):
		self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
		self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
		(fd, tempfilename) = tempfile.mkstemp(prefix="wifi-radar.conf.")
		fp = os.fdopen(fd, "w")
		# write DEFAULT section first
		if self._defaults:
			fp.write("[DEFAULT]\n")
			for key in sorted(self._defaults.keys()):
				fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
			fp.write("\n")
		# write other non-profile sections next
		for section in self._sections:
			if section not in self.profiles():
				fp.write("[%s]\n" % section)
				for key in sorted(self._sections[section].keys()):
					if key != "__name__":
						fp.write("%s = %s\n" %
								(key, str(self._sections[section][key]).replace('\n', '\n\t')))
				fp.write("\n")
		# write profile sections
		for section in self._sections:
			if section in self.profiles():
				fp.write("[%s]\n" % section)
				for key in sorted(self._sections[section].keys()):
					if key != "__name__":
						fp.write("%s = %s\n" %
								(key, str(self._sections[section][key]).replace('\n', '\n\t')))
				fp.write("\n")
		fp.close()
		move(tempfilename, self.filename)

# Load our conf file and known profiles
# Defaults, these may get overridden by values found in the conf file.
config_defaults = { # The network interface you use.
					# Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
					'interface': "auto_detect",
					# How long should the scan for access points last?
					#'scan_timeout': '5',
					# X1000 Linux has a say command (text to speech) to announce connecting to networks.
					# Set the speak_up option to false if you do not have or want this.
					'speak_command': '/usr/bin/say',
					# Should I speak up when connecting to a network? (If you have a speech command)
					'speak_up': 'False',
					# You may set this to true for cards that require a "commit" command with iwconfig
					'commit_required': 'False',
					# You may set this to true for cards that require the interface to be brought up first
					'ifup_required': 'False',
					# set the location and verbosity of the log file
					'logfile': '/var/log/wifi-radar.log',
					'loglevel': '50',
					# Set the location of several important programs
					'iwlist_command': '/sbin/iwlist',
					'iwconfig_command': '/sbin/iwconfig',
					'ifconfig_command': '/sbin/ifconfig',
					'route_command': '/sbin/route',
					'auto_profile_order': '[]',
					'version': WIFI_RADAR_VERSION }

config_dhcp = { # DHCP client
				'command': '/sbin/dhcpcd',
				# How long to wait for an IP addr from DHCP server
				'timeout': '30',
				# Arguments to use with DHCP client on connect
				'args': '-D -o -i dhcp_client -t %(timeout)s',
				# Argument to use with DHCP client on disconnect
				'kill_args': '-k',
				# The file where DHCP client PID is written
				'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }

config_wpa = {  # WPA Supplicant
				'command': '/usr/sbin/wpa_supplicant',
				# Arguments to use with WPA Supplicant on connect
				'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
				# Arguments to use with WPA Supplicant on disconnect
				'kill_command': '',
				# Where the WPA Supplicant config file can be found
				'configuration': '/etc/wpa_supplicant.conf',
				# Driver to use with WPA Supplicant
				'driver': 'wext',
				# The file where WPA Supplicant PID is written
				'pidfile': '/var/run/wpa_supplicant.pid' }

# initialize config, with defaults
confFile = ConfigFile(CONF_FILE, config_defaults)
confFile.set_section("DHCP", config_dhcp)
confFile.set_section("WPA", config_wpa)

if not os.path.isfile( CONF_FILE ):
	confFile.set_bool_opt('DEFAULT.new_file', True)
else:
	if not os.access(CONF_FILE, os.R_OK):
		print "Can't open " + CONF_FILE + "."
		print "Are you root?"
		sys.exit()
	try:
		confFile.read()
	except NameError:
		error_dlg = ErrorDialog(None, "A configuration file from a pre-2.0 version of WiFi Radar was found at %s.\n\nWiFi Radar v2.0.x does not read configuration files from previous versions.  Because %s may contain information that you might wish to use when configuring WiFi Radar %s, rename this file and run the program again.\n" % (CONF_FILE, CONF_FILE, WIFI_RADAR_VERSION))
		del error_dlg
		print "ERROR: A configuration file from a pre-2.0 version of WiFi Radar was found at %s.\n\nWiFi Radar v2.0.x does not read configuration files from previous versions.  Because %s may contain information that you might wish to use when configuring WiFi Radar %s, rename this file and run the program again.\n" % (CONF_FILE, CONF_FILE, WIFI_RADAR_VERSION)
		sys.exit()
	except SyntaxError:
		error_dlg = ErrorDialog(None, "A configuration file from a pre-2.0 version of WiFi Radar was found at %s.\n\nWiFi Radar v2.0.x does not read configuration files from previous versions.  The old configuration file is probably empty and can be removed.  Rename %s if you want to be very careful.  After removing or renaming %s, run this program again.\n" % (CONF_FILE, CONF_FILE, CONF_FILE))
		del error_dlg
		print "ERROR: A configuration file from a pre-2.0 version of WiFi Radar was found at %s.\n\nWiFi Radar v2.0.x does not read configuration files from previous versions.  The old configuration file is probably empty and can be removed.  Rename %s if you want to be very careful.  After removing or renaming %s, run this program again.\n" % (CONF_FILE, CONF_FILE, CONF_FILE)
		sys.exit()


####################################################################################################
# Embedded Images
wifi_radar_icon = [ ""
  "GdkP"
  "\0\0\22""7"
  "\2\1\0\2"
  "\0\0\1\214"
  "\0\0\0c"
  "\0\0\0O"
  "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
  "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
  "\377\0\7\0\0\0\10\0\0\0\25\0\0\0\35\0\0\0%\0\0\0-\0\0\0\"\0\0\0\11\327"
  "\377\377\377\0\6\0\0\0\"\0\0\0_\0\0\0\213\0\0\0\266\0\0\0\341\0\0\0\376"
  "\206\0\0\0\377\6\0\0\0\356\0\0\0\324\0\0\0\265\0\0\0~\0\0\0@\0\0\0\10"
  "\315\377\377\377\0\4\0\0\0\2\0\0\0;\0\0\0\210\0\0\0\325\221\0\0\0\377"
  "\4\0\0\0\371\0\0\0\303\0\0\0w\0\0\0\31\310\377\377\377\0\3\0\0\0\6\0"
  "\0\0m\0\0\0\342\227\0\0\0\377\4\0\0\0\374\0\0\0\264\0\0\0Q\0\0\0\5\303"
  "\377\377\377\0\3\0\0\0\4\0\0\0d\0\0\0\341\234\0\0\0\377\3\0\0\0\341\0"
  "\0\0`\0\0\0\2\277\377\377\377\0\3\0\0\0\2\0\0\0[\0\0\0\333\240\0\0\0"
  "\377\2\0\0\0\323\0\0\0K\274\377\377\377\0\3\0\0\0\1\0\0\0R\0\0\0\324"
  "\244\0\0\0\377\2\0\0\0\276\0\0\0#\271\377\377\377\0\2\0\0\0\31\0\0\0"
  "\277\247\0\0\0\377\2\0\0\0\363\0\0\0c\267\377\377\377\0\2\0\0\0/\0\0"
  "\0\343\252\0\0\0\377\2\0\0\0\257\0\0\0\24\264\377\377\377\0\2\0\0\0M"
  "\0\0\0\363\220\0\0\0\377\14\0\0\0\357\0\0\0\304\0\0\0\230\0\0\0v\0\0"
  "\0l\0\0\0c\0\0\0[\0\0\0j\0\0\0\205\0\0\0\240\0\0\0\311\0\0\0\373\220"
  "\0\0\0\377\2\0\0\0\346\0\0\0""4\262\377\377\377\0\2\0\0\0q\0\0\0\375"
  "\215\0\0\0\377\4\0\0\0\373\0\0\0\300\0\0\0t\0\0\0)\213\377\377\377\0"
  "\4\0\0\0\14\0\0\0E\0\0\0\205\0\0\0\334\216\0\0\0\377\2\0\0\0\363\0\0"
  "\0D\257\377\377\377\0\2\0\0\0\4\0\0\0\230\215\0\0\0\377\3\0\0\0\372\0"
  "\0\0\231\0\0\0\34\221\377\377\377\0\4\0\0\0\1\0\0\0C\0\0\0\251\0\0\0"
  "\372\214\0\0\0\377\2\0\0\0\371\0\0\0W\255\377\377\377\0\2\0\0\0\17\0"
  "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
  "\0\2\0\0\0\"\0\0\0\252\214\0\0\0\377\2\0\0\0\375\0\0\0k\253\377\377\377"
  "\0\2\0\0\0\25\0\0\0\324\213\0\0\0\377\3\0\0\0\376\0\0\0\252\0\0\0(\232"
  "\377\377\377\0\2\0\0\0""9\0\0\0\312\214\0\0\0\377\1\0\0\0\200\251\377"
  "\377\377\0\2\0\0\0\5\0\0\0\303\213\0\0\0\377\2\0\0\0\332\0\0\0""1\235"
  "\377\377\377\0\3\0\0\0\4\0\0\0\201\0\0\0\374\213\0\0\0\377\1\0\0\0p\250"
  "\377\377\377\0\1\0\0\0\222\213\0\0\0\377\2\0\0\0\301\0\0\0\22\240\377"
  "\377\377\0\2\0\0\0:\0\0\0\336\212\0\0\0\377\2\0\0\0\374\0\0\0I\246\377"
  "\377\377\0\1\0\0\0[\213\0\0\0\377\2\0\0\0\241\0\0\0\6\212\377\377\377"
  "\0\15\0\0\0\2\0\0\0&\0\0\0U\0\0\0\203\0\0\0\242\0\0\0\243\0\0\0\234\0"
  "\0\0\225\0\0\0\215\0\0\0\206\0\0\0}\0\0\0\\\0\0\0!\213\377\377\377\0"
  "\2\0\0\0\22\0\0\0\307\212\0\0\0\377\2\0\0\0\361\0\0\0+\244\377\377\377"
  "\0\2\0\0\0.\0\0\0\365\211\0\0\0\377\2\0\0\0\376\0\0\0|\211\377\377\377"
  "\0\4\0\0\0#\0\0\0d\0\0\0\223\0\0\0\277\214\0\0\0\310\4\0\0\0\253\0\0"
  "\0l\0\0\0-\0\0\0\2\210\377\377\377\0\2\0\0\0\12\0\0\0\267\212\0\0\0\377"
  "\2\0\0\0\336\0\0\0\24\242\377\377\377\0\2\0\0\0\20\0\0\0\334\211\0\0"
  "\0\377\2\0\0\0\367\0\0\0W\210\377\377\377\0\2\0\0\0#\0\0\0\211\223\0"
  "\0\0\310\3\0\0\0\266\0\0\0t\0\0\0\27\207\377\377\377\0\2\0\0\0\5\0\0"
  "\0\244\212\0\0\0\377\2\0\0\0\302\0\0\0\6\240\377\377\377\0\2\0\0\0\1"
  "\0\0\0\264\211\0\0\0\377\2\0\0\0\363\0\0\0""9\207\377\377\377\0\3\0\0"
  "\0\34\0\0\0\201\0\0\0\306\226\0\0\0\310\3\0\0\0\277\0\0\0Y\0\0\0\2\206"
  "\377\377\377\0\2\0\0\0\1\0\0\0\217\212\0\0\0\377\1\0\0\0\203\240\377"
  "\377\377\0\1\0\0\0\177\212\0\0\0\377\1\0\0\0T\206\377\377\377\0\3\0\0"
  "\0\25\0\0\0z\0\0\0\305\232\0\0\0\310\2\0\0\0\242\0\0\0*\207\377\377\377"
  "\0\1\0\0\0\243\211\0\0\0\377\2\0\0\0\372\0\0\0,\236\377\377\377\0\2\0"
  "\0\0D\0\0\0\375\211\0\0\0\377\1\0\0\0\213\206\377\377\377\0\2\0\0\0""8"
  "\0\0\0\274\235\0\0\0\310\3\0\0\0\306\0\0\0u\0\0\0\14\205\377\377\377"
  "\0\2\0\0\0\7\0\0\0\306\211\0\0\0\377\2\0\0\0\306\0\0\0\2\234\377\377"
  "\377\0\2\0\0\0\4\0\0\0\331\211\0\0\0\377\2\0\0\0\276\0\0\0\3\205\377"
  "\377\377\0\2\0\0\0T\0\0\0\306\214\0\0\0\310\10\0\0\0\260\0\0\0\202\0"
  "\0\0v\0\0\0~\0\0\0\207\0\0\0\217\0\0\0\227\0\0\0\264\214\0\0\0\310\2"
  "\0\0\0\264\0\0\0""2\205\377\377\377\0\2\0\0\0\27\0\0\0\341\211\0\0\0"
  "\377\1\0\0\0k\234\377\377\377\0\1\0\0\0c\211\0\0\0\377\2\0\0\0\343\0"
  "\0\0\26\204\377\377\377\0\2\0\0\0\2\0\0\0s\212\0\0\0\310\4\0\0\0\265"
  "\0\0\0s\0\0\0D\0\0\0\26\207\377\377\377\0\4\0\0\0\1\0\0\0+\0\0\0j\0\0"
  "\0\250\212\0\0\0\310\2\0\0\0\303\0\0\0A\205\377\377\377\0\2\0\0\0/\0"
  "\0\0\364\210\0\0\0\377\2\0\0\0\362\0\0\0\33\232\377\377\377\0\2\0\0\0"
  "\7\0\0\0\341\210\0\0\0\377\2\0\0\0\371\0\0\0""7\204\377\377\377\0\2\0"
  "\0\0\12\0\0\0\217\211\0\0\0\310\3\0\0\0\271\0\0\0]\0\0\0\10\216\377\377"
  "\377\0\3\0\0\0\36\0\0\0t\0\0\0\306\210\0\0\0\310\2\0\0\0\306\0\0\0P\205"
  "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
  "\0\0\0n\211\0\0\0\377\1\0\0\0h\204\377\377\377\0\2\0\0\0\20\0\0\0\245"
  "\210\0\0\0\310\3\0\0\0\274\0\0\0c\0\0\0\12\222\377\377\377\0\2\0\0\0"
  "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
  "\0\0\0\377\1\0\0\0:\230\377\377\377\0\2\0\0\0\13\0\0\0\350\210\0\0\0"
  "\377\1\0\0\0\250\204\377\377\377\0\2\0\0\0\3\0\0\0\230\210\0\0\0\310"
  "\2\0\0\0\213\0\0\0\15\225\377\377\377\0\3\0\0\0\2\0\0\0Z\0\0\0\277\210"
  "\0\0\0\310\1\0\0\0U\204\377\377\377\0\2\0\0\0%\0\0\0\370\210\0\0\0\377"
  "\1\0\0\0\265\230\377\377\377\0\1\0\0\0y\210\0\0\0\377\2\0\0\0\372\0\0"
  "\0\40\204\377\377\377\0\1\0\0\0o\210\0\0\0\310\2\0\0\0o\0\0\0\2\230\377"
  "\377\377\0\2\0\0\0\30\0\0\0\226\207\0\0\0\310\2\0\0\0\306\0\0\0""7\204"
  "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
  "\0\0\0\20\0\0\0\356\210\0\0\0\377\1\0\0\0\226\204\377\377\377\0\1\0\0"
  "\0C\207\0\0\0\310\2\0\0\0\305\0\0\0R\233\377\377\377\0\2\0\0\0\5\0\0"
  "\0\210\207\0\0\0\310\2\0\0\0\273\0\0\0\37\203\377\377\377\0\2\0\0\0\6"
  "\0\0\0\325\210\0\0\0\377\1\0\0\0\251\226\377\377\377\0\1\0\0\0\204\210"
  "\0\0\0\377\2\0\0\0\366\0\0\0\32\203\377\377\377\0\2\0\0\0!\0\0\0\277"
  "\206\0\0\0\310\2\0\0\0\275\0\0\0""8\235\377\377\377\0\2\0\0\0\2\0\0\0"
  "|\207\0\0\0\310\2\0\0\0\254\0\0\0\15\203\377\377\377\0\1\0\0\0J\210\0"
  "\0\0\377\2\0\0\0\375\0\0\0&\224\377\377\377\0\2\0\0\0\26\0\0\0\364\210"
  "\0\0\0\377\1\0\0\0\214\203\377\377\377\0\2\0\0\0\12\0\0\0\251\206\0\0"
  "\0\310\2\0\0\0\305\0\0\0""0\240\377\377\377\0\1\0\0\0r\207\0\0\0\310"
  "\1\0\0\0[\204\377\377\377\0\1\0\0\0\317\210\0\0\0\377\1\0\0\0\236\224"
  "\377\377\377\0\1\0\0\0\204\210\0\0\0\377\2\0\0\0\362\0\0\0\24\203\377"
  "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
  "\0\0\5\0\0\0$\0\0\0G\0\0\0X\0\0\0T\0\0\0O\0\0\0K\0\0\0B\0\0\0\35\214"
  "\377\377\377\0\2\0\0\0\2\0\0\0\214\206\0\0\0\310\2\0\0\0\307\0\0\0""1"
  "\203\377\377\377\0\1\0\0\0V\210\0\0\0\377\2\0\0\0\372\0\0\0\27\223\377"
  "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
  "\0\0\0@\207\0\0\0\310\1\0\0\0\204\212\377\377\377\0\4\0\0\0\7\0\0\0E"
  "\0\0\0u\0\0\0\222\210\0\0\0\226\4\0\0\0\204\0\0\0T\0\0\0$\0\0\0\1\211"
  "\377\377\377\0\2\0\0\0\12\0\0\0\245\206\0\0\0\310\2\0\0\0\251\0\0\0\5"
  "\202\377\377\377\0\2\0\0\0\2\0\0\0\331\210\0\0\0\377\1\0\0\0C\223\377"
  "\377\377\0\1\0\0\0\342\207\0\0\0\377\2\0\0\0\356\0\0\0\17\202\377\377"
  "\377\0\2\0\0\0\2\0\0\0\246\206\0\0\0\310\2\0\0\0\246\0\0\0\11\210\377"
  "\377\377\0\3\0\0\0\5\0\0\0D\0\0\0\212\216\0\0\0\226\2\0\0\0z\0\0\0\40"
  "\211\377\377\377\0\2\0\0\0\32\0\0\0\274\206\0\0\0\310\1\0\0\0d\203\377"
  "\377\377\0\1\0\0\0a\210\0\0\0\377\1\0\0\0b\222\377\377\377\0\2\0\0\0"
  "\10\0\0\0\375\207\0\0\0\377\1\0\0\0x\203\377\377\377\0\1\0\0\0G\206\0"
  "\0\0\310\2\0\0\0\275\0\0\0\36\210\377\377\377\0\2\0\0\0""3\0\0\0\207"
  "\221\0\0\0\226\3\0\0\0\225\0\0\0X\0\0\0\11\210\377\377\377\0\1\0\0\0"
  "R\206\0\0\0\310\2\0\0\0\302\0\0\0\23\202\377\377\377\0\2\0\0\0\5\0\0"
  "\0\342\207\0\0\0\377\1\0\0\0\201\223\377\377\377\0\1\0\0\0m\206\0\0\0"
  "\377\2\0\0\0\321\0\0\0\12\202\377\377\377\0\2\0\0\0\3\0\0\0\254\206\0"
  "\0\0\310\1\0\0\0J\207\377\377\377\0\2\0\0\0\1\0\0\0O\210\0\0\0\226\1"
  "\0\0\0\206\202\0\0\0h\3\0\0\0m\0\0\0s\0\0\0\214\207\0\0\0\226\2\0\0\0"
  "\210\0\0\0)\207\377\377\377\0\2\0\0\0\1\0\0\0\233\206\0\0\0\310\1\0\0"
  "\0l\203\377\377\377\0\2\0\0\0P\0\0\0\374\205\0\0\0\377\2\0\0\0\337\0"
  "\0\0\"\224\377\377\377\0\1\0\0\0s\204\0\0\0\377\2\0\0\0\315\0\0\0\23"
  "\203\377\377\377\0\1\0\0\0N\206\0\0\0\310\2\0\0\0\245\0\0\0\2\206\377"
  "\377\377\0\2\0\0\0\6\0\0\0f\206\0\0\0\226\3\0\0\0w\0\0\0""7\0\0\0\23"
  "\205\377\377\377\0\4\0\0\0\3\0\0\0*\0\0\0[\0\0\0\212\205\0\0\0\226\2"
  "\0\0\0\222\0\0\0*\207\377\377\377\0\2\0\0\0#\0\0\0\304\205\0\0\0\310"
  "\2\0\0\0\277\0\0\0\16\203\377\377\377\0\2\0\0\0]\0\0\0\376\203\0\0\0"
  "\377\2\0\0\0\332\0\0\0\35\226\377\377\377\0\5\0\0\0;\0\0\0j\0\0\0\223"
  "\0\0\0\244\0\0\0\20\203\377\377\377\0\2\0\0\0\5\0\0\0\260\206\0\0\0\310"
  "\1\0\0\0>\206\377\377\377\0\2\0\0\0\14\0\0\0z\205\0\0\0\226\2\0\0\0|"
  "\0\0\0/\213\377\377\377\0\3\0\0\0\10\0\0\0U\0\0\0\224\204\0\0\0\226\2"
  "\0\0\0\221\0\0\0%\207\377\377\377\0\1\0\0\0s\206\0\0\0\310\1\0\0\0d\204"
  "\377\377\377\0\5\0\0\0a\0\0\0\240\0\0\0\177\0\0\0]\0\0\0\26\237\377\377"
  "\377\0\1\0\0\0U\206\0\0\0\310\1\0\0\0\235\206\377\377\377\0\2\0\0\0\2"
  "\0\0\0r\204\0\0\0\226\3\0\0\0\225\0\0\0J\0\0\0\1\216\377\377\377\0\2"
  "\0\0\0\35\0\0\0w\204\0\0\0\226\2\0\0\0\217\0\0\0\40\206\377\377\377\0"
  "\2\0\0\0\27\0\0\0\304\205\0\0\0\310\2\0\0\0\273\0\0\0\12\247\377\377"
  "\377\0\1\0\0\0\236\206\0\0\0\310\1\0\0\0""5\206\377\377\377\0\1\0\0\0"
  "T\204\0\0\0\226\2\0\0\0\221\0\0\0""3\221\377\377\377\0\2\0\0\0\4\0\0"
  "\0l\204\0\0\0\226\2\0\0\0\215\0\0\0\34\206\377\377\377\0\1\0\0\0}\206"
  "\0\0\0\310\1\0\0\0E\247\377\377\377\0\1\0\0\0\276\205\0\0\0\310\1\0\0"
  "\0\224\206\377\377\377\0\1\0\0\0""4\204\0\0\0\226\2\0\0\0\214\0\0\0\40"
  "\223\377\377\377\0\2\0\0\0\5\0\0\0q\204\0\0\0\226\2\0\0\0\211\0\0\0\14"
  "\205\377\377\377\0\2\0\0\0\37\0\0\0\306\205\0\0\0\310\1\0\0\0`\246\377"
  "\377\377\0\2\0\0\0\12\0\0\0\277\205\0\0\0\310\1\0\0\0+\205\377\377\377"
  "\0\2\0\0\0\30\0\0\0\220\203\0\0\0\226\2\0\0\0\225\0\0\0*\225\377\377"
  "\377\0\2\0\0\0\10\0\0\0v\204\0\0\0\226\1\0\0\0X\206\377\377\377\0\1\0"
  "\0\0\207\205\0\0\0\310\1\0\0\0m\247\377\377\377\0\2\0\0\0""3\0\0\0\301"
  "\203\0\0\0\310\1\0\0\0[\206\377\377\377\0\1\0\0\0n\204\0\0\0\226\1\0"
  "\0\0G\227\377\377\377\0\2\0\0\0\12\0\0\0z\203\0\0\0\226\2\0\0\0\224\0"
  "\0\0\27\205\377\377\377\0\2\0\0\0\20\0\0\0\246\203\0\0\0\310\2\0\0\0"
  "\224\0\0\0\11\250\377\377\377\0\4\0\0\0,\0\0\0h\0\0\0\210\0\0\0R\206"
  "\377\377\377\0\1\0\0\0&\204\0\0\0\226\2\0\0\0f\0\0\0\1\230\377\377\377"
  "\0\2\0\0\0\26\0\0\0\224\203\0\0\0\226\1\0\0\0g\206\377\377\377\0\5\0"
  "\0\0\22\0\0\0\206\0\0\0y\0\0\0]\0\0\0\6\263\377\377\377\0\1\0\0\0t\203"
  "\0\0\0\226\2\0\0\0\216\0\0\0\13\232\377\377\377\0\1\0\0\0X\204\0\0\0"
  "\226\1\0\0\0#\274\377\377\377\0\1\0\0\0-\204\0\0\0\226\1\0\0\0K\233\377"
  "\377\377\0\2\0\0\0\15\0\0\0\217\203\0\0\0\226\1\0\0\0v\274\377\377\377"
  "\0\1\0\0\0t\203\0\0\0\226\2\0\0\0\213\0\0\0\10\213\377\377\377\0\5\0"
  "\0\0\5\0\0\0\30\0\0\0\40\0\0\0\36\0\0\0\22\214\377\377\377\0\1\0\0\0"
  "J\204\0\0\0\226\1\0\0\0*\273\377\377\377\0\1\0\0\0`\203\0\0\0\226\1\0"
  "\0\0E\212\377\377\377\0\3\0\0\0\13\0\0\0@\0\0\0Y\204\0\0\0Z\3\0\0\0Q"
  "\0\0\0""1\0\0\0\5\211\377\377\377\0\2\0\0\0\6\0\0\0\207\203\0\0\0\226"
  "\1\0\0\0\26\273\377\377\377\0\5\0\0\0""1\0\0\0\226\0\0\0\224\0\0\0n\0"
  "\0\0\5\211\377\377\377\0\2\0\0\0$\0\0\0U\202\0\0\0Z\4\0\0\0P\0\0\0E\0"
  "\0\0I\0\0\0X\202\0\0\0Z\2\0\0\0P\0\0\0\33\211\377\377\377\0\4\0\0\0""3"
  "\0\0\0\206\0\0\0\226\0\0\0\201\274\377\377\377\0\3\0\0\0\6\0\0\0""8\0"
  "\0\0\13\211\377\377\377\0\2\0\0\0\7\0\0\0A\202\0\0\0Z\2\0\0\0I\0\0\0"
  "\20\203\377\377\377\0\6\0\0\0\4\0\0\0\37\0\0\0O\0\0\0Z\0\0\0Y\0\0\0\36"
  "\212\377\377\377\0\2\0\0\0\34\0\0\0)\310\377\377\377\0\5\0\0\0<\0\0\0"
  "Z\0\0\0Y\0\0\0.\0\0\0\2\206\377\377\377\0\5\0\0\0\3\0\0\0;\0\0\0Z\0\0"
  "\0X\0\0\0\32\322\377\377\377\0\1\0\0\0\34\202\0\0\0Z\1\0\0\0\30\211\377"
  "\377\377\0\5\0\0\0\1\0\0\0>\0\0\0Z\0\0\0W\0\0\0\13\320\377\377\377\0"
  "\4\0\0\0\5\0\0\0P\0\0\0Z\0\0\0""5\213\377\377\377\0\4\0\0\0\2\0\0\0H"
  "\0\0\0Z\0\0\0:\320\377\377\377\0\4\0\0\0""4\0\0\0Z\0\0\0P\0\0\0\5\214"
  "\377\377\377\0\1\0\0\0\26\202\0\0\0Z\1\0\0\0\22\317\377\377\377\0\3\0"
  "\0\0+\0\0\0X\0\0\0\33\216\377\377\377\0\3\0\0\0>\0\0\0I\0\0\0\23\320"
  "\377\377\377\0\1\0\0\0\12\217\377\377\377\0\2\0\0\0\6\0\0\0\1\377\377"
  "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
  "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
  "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
  "\0"]

known_profile_icon = [ ""
  "GdkP"
  "\0\0\5""0"
  "\2\1\0\2"
  "\0\0\0P"
  "\0\0\0\24"
  "\0\0\0\24"
  "\210\0\0\0\0\4\0\0\0\3\0\0\0\16\0\0\0\23\0\0\0\11\216\0\0\0\0\11\0\0"
  "\0\16\0\0\0h\0\0\0\301\0\0\0\345\0\0\0\352\0\0\0\331\0\0\0\237\0\0\0"
  "9\0\0\0\3\212\0\0\0\0\13\0\0\0@\0\0\0\323\0\0\0\376\0\0\0\350\0\0\0\304"
  "\0\0\0\271\0\0\0\323\0\0\0\367\0\0\0\370\0\0\0\227\0\0\0\17\210\0\0\0"
  "\0\15\0\0\0K\0\0\0\354\0\0\0\365\0\0\0\206\0\0\0#\0\0\0\6\0\0\0\3\0\0"
  "\0\15\0\0\0C\0\0\0\304\0\0\0\376\0\0\0\260\0\0\0\22\206\0\0\0\0\17\0"
  "\0\0""2\0\0\0\346\0\0\0\351\0\0\0L\0\0\0#\0\0\0u\0\0\0\246\0\0\0\257"
  "\0\0\0\223\0\0\0M\0\0\0\27\0\0\0\235\0\0\0\375\0\0\0\242\0\0\0\7\204"
  "\0\0\0\0\20\0\0\0\13\0\0\0\300\0\0\0\372\0\0\0W\0\0\0O\0\0\0\271\0\0"
  "\0\233\0\0\0b\0\0\0V\0\0\0z\0\0\0\267\0\0\0\223\0\0\0$\0\0\0\267\0\0"
  "\0\374\0\0\0X\204\0\0\0\0\7\0\0\0S\0\0\0\374\0\0\0\240\0\0\0H\0\0\0\275"
  "\0\0\0a\0\0\0\12\202\0\0\0\0\10\0\0\0\1\0\0\0%\0\0\0\240\0\0\0\241\0"
  "\0\0""9\0\0\0\352\0\0\0\320\0\0\0\12\203\0\0\0\0\21\0\0\0\262\0\0\0\351"
  "\0\0\0A\0\0\0\272\0\0\0g\0\0\0\6\0\0\0""4\0\0\0e\0\0\0l\0\0\0T\0\0\0"
  "\25\0\0\0\27\0\0\0\251\0\0\0v\0\0\0\214\0\0\0\367\0\0\0<\203\0\0\0\0"
  "\21\0\0\0""6\0\0\0G\0\0\0r\0\0\0\244\0\0\0\17\0\0\0P\0\0\0b\0\0\0#\0"
  "\0\0\27\0\0\0;\0\0\0s\0\0\0\33\0\0\0E\0\0\0\270\0\0\0""6\0\0\0\\\0\0"
  "\0\15\205\0\0\0\0\15\0\0\0T\0\0\0""8\0\0\0""0\0\0\0f\0\0\0\6\0\0\0\0"
  "\0\0\0\1\0\0\0\0\0\0\0(\0\0\0l\0\0\0\13\0\0\0k\0\0\0\33\206\0\0\0\0\16"
  "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
  "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
  "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
  "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
  "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
  "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
  "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
  "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
  "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
  "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
  "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
  "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
  "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
  "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
  "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
  "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
  "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
  "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
  "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
  "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
  "\313\377\272\272\272\377\24\24\24\226\0\0\0\30\0\0\0\10\0\0\0\5\0\0\0"
  "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
  "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
  "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
  "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
  "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
  "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
  "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
  "\377\231\231\231\376\16\16\16\240\0\0\0\35\0\0\0\6\0\0\0\2\0\0\0\12\0"
  "\0\0/\0\0\0n\0\0\0|\0\0\0\177\202\0\0\0\200\202\0\0\0\201\1\0\0\0\203"
  "\204\0\0\0\205\12\0\0\0\201\0\0\0y\0\0\0<\0\0\0\15\0\0\0\2\0\0\0\0\0"
  "\0\0\2\0\0\0\6\0\0\0\14\0\0\0\20\204\0\0\0\24\202\0\0\0\25\203\0\0\0"
  "\26\6\0\0\0\25\0\0\0\22\0\0\0\15\0\0\0\7\0\0\0\2\0\0\0\0"]

unknown_profile_icon = [ ""
  "GdkP"
  "\0\0\5\22"
  "\2\1\0\2"
  "\0\0\0P"
  "\0\0\0\24"
  "\0\0\0\24"
  "\210\0\0\0\0\4\0\0\0\1\0\0\0\4\0\0\0\6\0\0\0\3\216\0\0\0\0\11\0\0\0\4"
  "\0\0\0\37\0\0\0""9\0\0\0D\0\0\0F\0\0\0@\0\0\0/\0\0\0\21\0\0\0\1\212\0"
  "\0\0\0\7\0\0\0\23\0\0\0\77\0\0\0K\0\0\0E\0\0\0:\0\0\0""7\0\0\0\77\202"
  "\0\0\0I\2\0\0\0-\0\0\0\4\210\0\0\0\0\15\0\0\0\26\0\0\0F\0\0\0I\0\0\0"
  "(\0\0\0\13\0\0\0\2\0\0\0\1\0\0\0\4\0\0\0\24\0\0\0:\0\0\0K\0\0\0""4\0"
  "\0\0\6\206\0\0\0\0\17\0\0\0\17\0\0\0D\0\0\0E\0\0\0\26\0\0\0\13\0\0\0"
  "#\0\0\0""1\0\0\0""4\0\0\0,\0\0\0\27\0\0\0\7\0\0\0/\0\0\0K\0\0\0""0\0"
  "\0\0\2\204\0\0\0\0\20\0\0\0\3\0\0\0""9\0\0\0J\0\0\0\32\0\0\0\30\0\0\0"
  "7\0\0\0.\0\0\0\35\0\0\0\32\0\0\0$\0\0\0""6\0\0\0,\0\0\0\13\0\0\0""6\0"
  "\0\0K\0\0\0\32\204\0\0\0\0\7\0\0\0\31\0\0\0K\0\0\0""0\0\0\0\25\0\0\0"
  "8\0\0\0\35\0\0\0\3\202\0\0\0\0\2\0\0\0\1\0\0\0\13\202\0\0\0""0\4\0\0"
  "\0\21\0\0\0F\0\0\0>\0\0\0\3\203\0\0\0\0\21\0\0\0""5\0\0\0E\0\0\0\23\0"
  "\0\0""7\0\0\0\37\0\0\0\2\0\0\0\20\0\0\0\36\0\0\0\40\0\0\0\31\0\0\0\6"
  "\0\0\0\7\0\0\0""2\0\0\0#\0\0\0)\0\0\0I\0\0\0\22\203\0\0\0\0\21\0\0\0"
  "\20\0\0\0\25\0\0\0\"\0\0\0""1\0\0\0\4\0\0\0\30\0\0\0\35\0\0\0\13\0\0"
  "\0\7\0\0\0\21\0\0\0\"\0\0\0\10\0\0\0\25\0\0\0""6\0\0\0\20\0\0\0\33\0"
  "\0\0\4\205\0\0\0\0\15\0\0\0\31\0\0\0\21\0\0\0\16\0\0\0\36\0\0\0\2\0\0"
  "\0\0\0\0\0\1\0\0\0\0\0\0\0\14\0\0\0\40\0\0\0\3\0\0\0\40\0\0\0\10\206"
  "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
  "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
  "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
  "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
  "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
  "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
  "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
  "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
  "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
  "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
  "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
  "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
  "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
  "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
  "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
  "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
  "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
  "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
  "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
  "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
  "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
  "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
  "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
  "\16\16""0\0\0\0\10\0\0\0\2\0\0\0\1\0\0\0\3\0\0\0\16\0\0\0!\0\0\0%\205"
  "\0\0\0&\205\0\0\0'\12\0\0\0&\0\0\0$\0\0\0\22\0\0\0\4\0\0\0\1\0\0\0\0"
  "\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4\206\0\0\0\6\203\0\0\0\7\202\0\0\0\6"
  "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]

signal_xpm_barely = [
"20 20 10 1",
" 	c None",
".	c #C6C6C6",
"+	c #CCCCCC",
"@	c #DBDBDB",
"#	c #D3D3D3",
"$	c #A9B099",
"%	c #95A173",
"&	c #6B8428",
"*	c #B4B7AC",
"=	c #80924D",
"               .+++.",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"          .++++#@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"     $%%%%#@@@@@@@@+",
"     %&&&&@@@@@@@@@+",
"     %&&&&@@@@@@@@@+",
"     %&&&&@@@@@@@@@+",
"     %&&&&@@@@@@@@@+",
"*%%%%=&&&&@@@@@@@@@+",
"%&&&&&&&&&@@@@@@@@@+",
"%&&&&&&&&&@@@@@@@@@+",
"%&&&&&&&&&@@@@@@@@@+",
"*%%%%%%%%%+++++++++."
]


signal_xpm_best = [
"20 20 6 1",
" 	c None",
".	c #9DAABF",
"+	c #7B96BF",
"@	c #386EBF",
"#	c #5982BF",
"$	c #AEB4BF",
"               .+++.",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"          .++++#@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"     .++++#@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"$++++#@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"$++++++++++++++++++."
]

signal_xpm_none = [
"20 20 6 1",
" 	c None",
".	c #C6C6C6",
"+	c #CCCCCC",
"@	c #DBDBDB",
"#	c #D3D3D3",
"$	c #C2C2C2",
"               .+++.",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"          .++++#@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"     .++++#@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"$++++#@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"+@@@@@@@@@@@@@@@@@@+",
"$++++++++++++++++++."
]

signal_xpm_ok = [
"20 20 10 1",
" 	c None",
".	c #C6C6C6",
"+	c #CCCCCC",
"@	c #DBDBDB",
"#	c #A1A5B2",
"$	c #848DA5",
"%	c #D3D3D3",
"&	c #4A5B8C",
"*	c #677498",
"=	c #B0B2B8",
"               .+++.",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"          #$$$$%@@@+",
"          $&&&&@@@@+",
"          $&&&&@@@@+",
"          $&&&&@@@@+",
"          $&&&&@@@@+",
"     #$$$$*&&&&@@@@+",
"     $&&&&&&&&&@@@@+",
"     $&&&&&&&&&@@@@+",
"     $&&&&&&&&&@@@@+",
"     $&&&&&&&&&@@@@+",
"=$$$$*&&&&&&&&&@@@@+",
"$&&&&&&&&&&&&&&@@@@+",
"$&&&&&&&&&&&&&&@@@@+",
"$&&&&&&&&&&&&&&@@@@+",
"=$$$$$$$$$$$$$$++++."
]


signal_xpm_low = [
"20 20 8 1",
" 	c None",
".	c #C6C6C6",
"+	c #CCCCCC",
"@	c #DBDBDB",
"#	c #D3D3D3",
"$	c #BFB0B5",
"%	c #C18799",
"&	c #C54F74",
"               .+++.",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"               +@@@+",
"          .++++#@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"          +@@@@@@@@+",
"     .++++#@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"     +@@@@@@@@@@@@@+",
"$%%%%#@@@@@@@@@@@@@+",
"%&&&&@@@@@@@@@@@@@@+",
"%&&&&@@@@@@@@@@@@@@+",
"%&&&&@@@@@@@@@@@@@@+",
"$%%%%++++++++++++++."
]


####################################################################################################
# Make so we can be imported
if __name__ == "__main__":
	if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
		print "WiFi Radar version %s" % WIFI_RADAR_VERSION
	elif len( sys.argv ) > 1 and ( sys.argv[1] == '--help' or sys.argv[1] == '-h' ):
		print "WiFi Radar version %s" % WIFI_RADAR_VERSION
		print "For help, check man pages for wifi-radar and wifi-radar.conf,"
		print "or visit http://wifi-radar.berlios.de"
	else:
		import gtk, gobject
		gtk.gdk.threads_init()
		apQueue = Queue.Queue(100)
		commQueue = Queue.Queue(2)
		
		logger = logging.getLogger("wrlog")
		logger.setLevel(confFile.get_opt_as_int('DEFAULT.loglevel'))
		try:
			fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
		except IOError, (error_number, error_str):
			error_dlg = ErrorDialog(None, "Cannot open log file for writing: %s.\n\nWiFi Radar will work, but a log file will not be recorded." % (error_str))
			del error_dlg
		else:
			fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
			logger.addHandler(fileLogHandler)
		consoleLogHandler = logging.StreamHandler()
		consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
		logger.addHandler(consoleLogHandler)
		if __debug__:
			logger.setLevel(logging.INFO)
		
		exit_event = threading.Event()
		exit_event.clear()
		threading.Thread(None, scanning_thread, None, (confFile, apQueue, commQueue, logger, exit_event)).start()
		main_radar_window = radar_window(confFile, apQueue, commQueue, logger, exit_event)
		gobject.timeout_add( 500, main_radar_window.update_window )
		main_radar_window.main()
