Import errno module
[wifi-radar.git] / wifi-radar
blob158f320ba8e3374b41099b9b15855bb388f9c0d4
1 #!/usr/bin/python
3 # A wireless profile manager for Linux
5 # Originally created for x1000 Linux:
6 # http://x1000.bitbuilder.com
8 # Created by:
9 # Ahmad Baitalmal <ahmad@baitalmal.com>
11 # Maintained 2006-2009 by:
12 # Brian Elliott Finley <brian@thefinleys.com>
14 # Maintained by:
15 # Sean Robinson <seankrobinson@gmail.com>
17 # License:
18 # GPL
20 # http://wifi-radar.berlios.de
22 # See CREDITS file for more contributors.
23 # See ChangeLog file for, well, changes.
25 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
26 # turn on console debugging.
28 import ConfigParser
29 import errno
30 import gtk
31 import logging
32 import logging.handlers
33 import os
34 import Queue
35 import re
36 import string
37 import sys
38 import threading
39 from signal import SIGTERM
40 from subprocess import call, Popen, PIPE
41 from time import sleep
42 from types import *
44 WIFI_RADAR_VERSION = "0.0.0"
47 # Where the conf file should live could be different for your distro. Please change
48 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
50 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
52 os.environ['LC_MESSAGES'] = 'C'
55 #####################################
56 # Labels
57 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
58 USE_IP_LABEL = "Manual network configuration"
59 WIFI_SET_LABEL = "WiFi Options"
60 CON_PP_LABEL = "Connection Commands"
61 DIS_PP_LABEL = "Disconnection Commands"
62 USE_WPA_LABEL = "Use WPA"
63 NO_WPA_LABEL = "No WPA"
64 ####################################################################################################
66 ####################################################################################################
67 ####################################################################################################
69 # Sets the interface to the specified network device
71 #Parameters:
73 # 'device' -- string - The network device to use
75 #Returns:
77 # nothing
78 def set_network_device( device ):
79 #print "set_network_device: ", device
80 if device != "auto_detect":
81 confFile.set_opt('DEFAULT.interface', device)
82 else: # auto detect network device
83 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
84 # If no devices are found, default to eth1.
85 # call iwconfig command and read output
86 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
87 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
88 if len(wireless_devices) > 0:
89 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
90 #else:
91 #print "No wifi-device found. Exiting."
92 #sys.exit()
94 # Return a blank profile
96 #Parameters:
98 # none
100 #Returns:
102 # dictionary -- An AP profile with defaults set.
103 def get_new_profile():
104 return { 'known': False,
105 'available': False,
106 'encrypted': False,
107 'essid': '',
108 'bssid': '00:00:00:00:00:00',
109 'protocol': 'g',
110 'signal': 0,
111 'channel': 'auto',
112 'con_prescript': '',
113 'con_postscript': '',
114 'dis_prescript': '',
115 'dis_postscript': '',
116 'key': '',
117 'mode': 'auto',
118 'security': '',
119 'use_wpa': False,
120 'wpa_driver': '',
121 'use_dhcp': True,
122 'ip': '',
123 'netmask': '',
124 'gateway': '',
125 'domain': '',
126 'dns1': '',
127 'dns2': ''
130 # Combine essid and bssid to make a config file section name
132 #Parameters:
134 # 'essid' -- string - AP ESSID
136 # 'bssid' -- string - AP BSSID
138 #Returns:
140 # string -- the bssid concatenated to a colon, concatenated to the essid
141 def make_section_name( essid, bssid ):
142 return essid + ':' + bssid
144 # Split a config file section name into an essid and a bssid
146 #Parameters:
148 # 'section' -- string - Config file section name
150 #Returns:
152 # list -- the essid and bssid
153 def split_section_name( section ):
154 parts = re.split(':', section)
155 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
157 # Run commands through the shell
159 #Parameters:
161 # 'command' -- tuple - The command and arguments to run.
163 # 'environment' -- dictionary - Environment variables (as keys) and their values.
165 #Returns:
167 # boolean -- True on success, otherwise, False
168 def shellcmd( command, environment = None ):
169 try:
170 env_tmp = os.environ
171 env_tmp.update(environment)
172 command = ' '.join(command)
173 return_code = call(command, shell=True, env=env_tmp)
174 if return_code >= 0:
175 return True
176 else:
177 print >>sys.stderr, "Child was terminated by signal", -return_code
178 except OSError, exception:
179 print >>sys.stderr, "Execution failed:", exception
180 return False
182 # Speak feedback message to user
184 #Parameters:
186 # 'words' -- string - Message to speak to user
188 #Returns:
190 # nothing
191 def say( words ):
192 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
193 words = words.replace( "\"", "\\\"" )
194 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
196 # Scan for a limited time and return AP names and bssid found.
197 # Access points we find will be put on the outgoing Queue, apQueue.
199 #Parameters:
201 # 'confFile' -- ConfigFile - Config file object
203 # 'apQueue' -- Queue - Queue on which to put AP profiles
205 # 'commandQueue' -- Queue - Queue from which to read commands
207 # 'logger' -- Logger - Python's logging facility
209 #Returns:
211 # nothing
212 def scanning_thread( confFile, apQueue, commandQueue, logger ):
213 # Setup our essid pattern matcher
214 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
215 bssid_pattern = re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
216 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S )
217 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
218 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
219 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
220 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
222 access_points = {}
223 command = "scan"
224 while True:
225 try:
226 command = commandQueue.get_nowait()
227 logger.debug("received command: %s" % ( command, ))
228 command_read = True
229 except Queue.Empty:
230 command_read = False
231 if command == "scan":
232 #logger.debug("Beginning scan pass")
233 # Some cards need to have the interface up to scan
234 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
235 # call ifconfig command and wait for return
236 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
237 # update the signal strengths
238 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
239 #logger.debug("Current IP: %s\nCurrent ESSID: %s\nCurrent BSSID: %s" % (get_current_ip(), get_current_essid(), get_current_bssid()))
240 # zero out the signal levels for all access points
241 for bssid in access_points:
242 access_points[bssid]['signal'] = 0
243 # split the scan data based on the address line
244 hits = scandata.split(' - ')
245 for hit in hits:
246 # set the defaults for profile template
247 profile = get_new_profile()
248 m = essid_pattern.search( hit )
249 if m:
250 # we found an essid
251 profile['essid'] = m.groups()[1]
252 m = bssid_pattern.search( hit ) # get BSSID from scan
253 if m: profile['bssid'] = m.groups()[1]
254 m = protocol_pattern.search( hit ) # get protocol from scan
255 if m: profile['protocol'] = m.groups()[1]
256 m = mode_pattern.search( hit ) # get mode from scan
257 if m: profile['mode'] = m.groups()[1]
258 m = channel_pattern.search( hit ) # get channel from scan
259 if m: profile['channel'] = m.groups()[1]
260 m = enckey_pattern.search( hit ) # get encryption key from scan
261 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
262 m = signal_pattern.search( hit ) # get signal strength from scan
263 if m: profile['signal'] = m.groups()[1]
264 access_points[ profile['bssid'] ] = profile
265 for bssid in access_points:
266 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
267 # Put all, now or previously, sensed access_points into apQueue
268 try:
269 apQueue.put_nowait( access_points[bssid] )
270 except Queue.Full:
271 pass
272 elif command == "exit":
273 logger.debug("Exiting")
274 return
275 if command_read: commandQueue.task_done()
276 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
277 sleep( 3 )
278 else:
279 sleep( 1 )
282 # Manage a connection; including reporting connection state,
283 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
284 class ConnectionManager():
285 # Create a new connection manager which can read a config file and send to scanning thread
286 # command Queue. A new manager checks for a pre-existing connection and takes
287 # its AP profile from the ESSID and BSSID to which it is currently attached.
289 #Parameters:
291 # 'confFile' -- ConfigFile - Config file object
293 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
295 # 'logger' -- Logger - Python's logging facility
297 #Returns:
299 # ConnectionManager instance
300 def __init__( self, confFile, commandQueue, logger ):
301 self.confFile = confFile
302 self.commQueue = commandQueue
303 self.logger = logger
304 # is connection running?
305 self.state = False
306 if self.get_current_ip():
307 self.state = True
308 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
310 # Change the interface state: up or down.
312 #Parameters:
314 # 'state' -- string - The state to which to change the interface.
316 #Returns:
318 # nothing
319 def if_change( self, state ):
320 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
321 self.logger.debug("changing interface state to %s" % ( state, ))
322 # call ifconfig command and wait for return
323 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
325 # Connect to the specified AP.
327 #Parameters:
329 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
331 # 'status' -- status implementer - Object which implements status interface.
333 #Returns:
335 # nothing
336 def connect_to_network( self, profile, status ):
337 self.profile = profile
338 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
339 say( msg )
340 self.logger.debug(msg)
341 # ready to dance
342 # Let's run the connection prescript
343 if self.profile['con_prescript'].strip() != '':
344 # got something to execute
345 # run connection prescript through shell and wait for return
346 self.logger.debug("executing connection prescript: %s" % ( self.profile['con_prescript'], ))
347 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
348 status.show()
349 # Some cards need to have the interface up
350 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
351 self.if_change('up')
352 # Start building iwconfig command line, command
353 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
354 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
355 # Setting essid
356 iwconfig_command.append( 'essid' )
357 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
358 # Setting nick
359 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
360 # Setting key
361 iwconfig_command.append( 'key' )
362 if self.profile['key'] == '':
363 iwconfig_command.append( 'off' )
364 else:
365 iwconfig_command.append( "'" + self.profile['key'] + "'" )
366 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
367 # Setting mode
368 if self.profile['mode'].lower() == 'master':
369 self.profile['mode'] = 'Managed'
370 iwconfig_command.append( 'mode' )
371 iwconfig_command.append( self.profile['mode'] )
372 # Setting channel
373 if self.profile['channel'] != '':
374 iwconfig_command.append( 'channel' )
375 iwconfig_command.append( self.profile['channel'] )
376 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
377 iwconfig_command.append( 'ap' )
378 iwconfig_command.append( self.profile['bssid'] )
379 # Some cards require a commit
380 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
381 self.logger.debug("iwconfig_args %s " % ( iwconfig_args, ))
382 iwconfig_command.append( 'commit' )
383 # call iwconfig command and wait for return
384 if not shellcmd(iwconfig_command): return
385 # Now normal network stuff
386 # Kill off any existing DHCP clients running
387 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
388 self.logger.debug("Killing existing DHCP...")
389 try:
390 if self.confFile.get_opt('DHCP.kill_args') != '':
391 # call DHCP client kill command and wait for return
392 self.logger.debug("%s %s", ( self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args'), ))
393 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
394 else:
395 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
396 except OSError:
397 print "failed to kill DHCP client"
398 sys.exit()
399 finally:
400 print "Stale pid file. Removing..."
401 os.remove(self.confFile.get_opt('DHCP.pidfile'))
402 # Kill off any existing WPA supplicants running
403 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
404 self.logger.debug("Killing existing WPA supplicant...")
405 try:
406 if not self.confFile.get_opt('WPA.kill_command') != '':
407 # call WPA supplicant kill command and wait for return
408 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
409 else:
410 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
411 except OSError:
412 print "failed to kill WPA supplicant"
413 sys.exit()
414 finally:
415 print "Stale pid file. Removing..."
416 os.remove(self.confFile.get_opt('WPA.pidfile'))
417 # Begin WPA supplicant
418 if self.profile['use_wpa'] :
419 self.logger.debug("WPA args: %s" % ( self.confFile.get_opt('WPA.args'), ))
420 status.update_message("WPA supplicant starting")
421 if sys.modules.has_key("gtk"):
422 while gtk.events_pending():
423 gtk.main_iteration(False)
424 # call WPA supplicant command and do not wait for return
425 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
426 if self.profile['use_dhcp'] :
427 self.logger.debug("Disable iwlist while dhcp in progress...")
428 try:
429 self.commQueue.put("pause")
430 except Queue.Full:
431 pass
432 status.update_message("Acquiring IP Address (DHCP)")
433 if sys.modules.has_key("gtk"):
434 while gtk.events_pending():
435 gtk.main_iteration(False)
436 # call DHCP client command and do not wait for return
437 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
438 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
439 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
440 dhcp_proc = Popen(dhcp_command, stdout=None)
441 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
442 tick = 0.25
443 waiting = dhcp_proc.poll()
444 while waiting == None:
445 waiting = dhcp_proc.poll()
446 if timer < 0:
447 os.kill(dhcp_proc.pid, SIGTERM)
448 break
449 if sys.modules.has_key("gtk"):
450 while gtk.events_pending():
451 gtk.main_iteration(False)
452 timer -= tick
453 sleep(tick)
454 # Re-enable iwlist
455 try:
456 self.commQueue.put("scan")
457 except Queue.Full:
458 pass
459 if not self.get_current_ip():
460 status.update_message("Could not get IP address!")
461 if sys.modules.has_key("gtk"):
462 while gtk.events_pending():
463 gtk.main_iteration(False)
464 sleep(1)
465 if self.state:
466 self.disconnect_interface()
467 status.hide()
468 return
469 else:
470 status.update_message("Got IP address. Done.")
471 self.state = True
472 if sys.modules.has_key("gtk"):
473 while gtk.events_pending():
474 gtk.main_iteration(False)
475 sleep(2)
476 else:
477 ifconfig_command= "%s %s down; %s %s %s netmask %s" % ( self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), self.profile['ip'], self.profile['netmask'] )
478 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
479 resolv_contents = ''
480 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
481 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
482 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
483 if ( resolv_contents != '' ):
484 resolv_file=open('/etc/resolv.conf', 'w')
485 resolv_file.write(s)
486 resolv_file.close
487 if not shellcmd([ifconfig_command]): return
488 if not shellcmd([route_command]): return
489 self.state = True
490 # Let's run the connection postscript
491 con_postscript = self.profile['con_postscript']
492 if self.profile['con_postscript'].strip() != '':
493 self.logger.debug("executing connection postscript: %s" % ( self.profile['con_postscript'], ))
494 shellcmd([self.profile['con_postscript']],
495 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
496 "WIFIRADAR_ESSID": self.get_current_essid() or '',
497 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
498 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
501 status.hide()
503 # Disconnect from the AP with which a connection has been established/attempted.
505 #Parameters:
507 # nothing
509 #Returns:
511 # nothing
512 def disconnect_interface( self ):
513 msg = "Disconnecting"
514 say( msg )
515 self.logger.debug(msg)
516 # Pause scanning while manipulating card
517 try:
518 self.commQueue.put("pause")
519 except Queue.Full:
520 pass
521 if self.state:
522 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
523 # Let's run the disconnection prescript
524 if self.profile['dis_prescript'].strip() != '':
525 self.logger.debug("executing disconnection prescript: %s" % ( self.profile['dis_prescript'], ))
526 shellcmd([self.profile['dis_prescript']],
527 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
528 "WIFIRADAR_ESSID": self.get_current_essid() or '',
529 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
530 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
533 self.logger.debug("Kill off any existing DHCP clients running...")
534 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
535 self.logger.debug("Killing existing DHCP...")
536 try:
537 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
538 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
539 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
540 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
541 self.logger.debug("DHCP command: %s" % ( dhcp_command, ))
542 # call DHCP client command and wait for return
543 if not shellcmd(dhcp_command): return
544 else:
545 self.logger.debug("Killing DHCP manually...")
546 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
547 except OSError:
548 print "failed to kill DHCP client"
549 self.logger.debug("Kill off any existing WPA supplicants running...")
550 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
551 self.logger.debug("Killing existing WPA supplicant...")
552 try:
553 if not self.confFile.get_opt('WPA.kill_command') != '':
554 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
555 if not shellcmd(wpa_command): return
556 else:
557 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
558 except OSError:
559 print "failed to kill WPA supplicant"
560 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
561 self.logger.debug("Let's clear out the wireless stuff")
562 self.logger.debug("Now take the interface down")
563 # taking down the interface too quickly can crash my system, so pause a moment
564 sleep(1)
565 self.if_change('down')
566 self.logger.debug("Since it may be brought back up by the next scan, lets unset its IP")
567 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
568 # Let's run the disconnection postscript
569 if self.profile['dis_postscript'].strip() != '':
570 self.logger.debug("executing disconnection postscript: %s" % ( self.profile['dis_postscript'], ))
571 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
572 self.state = False
573 self.logger.debug("Disconnect complete.")
574 # Begin scanning again
575 try:
576 self.commQueue.put("scan")
577 except Queue.Full:
578 pass
580 # Returns the current IP, if any, by calling ifconfig.
582 #Parameters:
584 # nothing
586 #Returns:
588 # string or None -- the IP address or None (if no there is no current connection)
589 def get_current_ip( self ):
590 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
591 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
592 # Be careful to the language (inet adr: in French for example)
594 # Hi Brian
596 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
597 # There the string in ifconfig is inet Adresse for the IP which isn't
598 # found by the current get_current_ip function in wifi-radar. I changed
599 # the according line (#289; gentoo, v1.9.6-r1) to
600 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
601 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
603 # I'd be happy if you could incorporate this small change because as now
604 # I've got to change the file every time it is updated.
606 # Best wishes
608 # Simon
609 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
610 line = ifconfig_info.read()
611 if ip_re.search( line ):
612 return ip_re.search( line ).group(1)
613 return None
615 # Returns the current ESSID, if any, by calling iwconfig.
617 #Parameters:
619 # nothing
621 #Returns:
623 # string or None -- the ESSID or None (if no there is no current association)
624 def get_current_essid( self ):
625 """Returns the current ESSID if any by calling iwconfig"""
626 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
627 # Be careful to the language (inet adr: in French for example)
628 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
629 line = iwconfig_info.read()
630 if essid_re.search( line ):
631 return essid_re.search( line ).group(2)
632 return None
634 # Returns the current BSSID, if any, by calling iwconfig.
636 #Parameters:
638 # nothing
640 #Returns:
642 # string or None -- the BSSID or None (if no there is no current association)
643 def get_current_bssid( self ):
644 """Returns the current BSSID if any by calling iwconfig"""
645 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
646 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
647 line = iwconfig_info.read()
648 if bssid_re.search( line ):
649 return bssid_re.search( line ).group(2)
650 return None
654 # The main user interface window for WiFi Radar. This class also is the control
655 # center for most of the rest of the operations.
656 class radar_window:
657 # Create a new radar_window.
659 #Parameters:
661 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
663 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
665 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
667 # 'logger' -- Logger - Python's logging facility
669 #Returns:
671 # radar_window instance
672 def __init__( self, confFile, apQueue, commQueue, logger ):
673 global signal_xpm_none
674 global signal_xpm_low
675 global signal_xpm_barely
676 global signal_xpm_ok
677 global signal_xpm_best
678 global known_profile_icon
679 global unknown_profile_icon
680 global wifi_radar_icon
682 self.confFile = confFile
683 self.apQueue = apQueue
684 self.commandQueue = commQueue
685 self.logger = logger
686 self.access_points = {}
687 self.connection = None
689 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
690 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
691 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
692 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
693 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
694 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
695 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
696 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
697 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
698 self.window.set_icon( icon )
699 self.window.set_border_width( 10 )
700 self.window.set_size_request( 550, 300 )
701 self.window.set_title( "WiFi Radar" )
702 self.window.connect( 'delete_event', self.delete_event )
703 self.window.connect( 'destroy', self.destroy )
704 # let's create all our widgets
705 self.current_network = gtk.Label()
706 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
707 self.current_network.show()
708 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
709 self.close_button.show()
710 self.close_button.connect( 'clicked', self.delete_event, None )
711 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
712 self.about_button.show()
713 self.about_button.connect( 'clicked', self.show_about_info, None )
714 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
715 self.preferences_button.show()
716 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
717 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
718 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
719 self.plist = gtk.TreeView( self.pstore )
720 # The icons column, known and encryption
721 self.pix_cell = gtk.CellRendererPixbuf()
722 self.wep_cell = gtk.CellRendererPixbuf()
723 self.icons_cell = gtk.CellRendererText()
724 self.icons_col = gtk.TreeViewColumn()
725 self.icons_col.pack_start( self.pix_cell, False )
726 self.icons_col.pack_start( self.wep_cell, False )
727 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
728 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
729 self.plist.append_column( self.icons_col )
730 # The AP column
731 self.ap_cell = gtk.CellRendererText()
732 self.ap_col = gtk.TreeViewColumn( "Access Point" )
733 self.ap_col.pack_start( self.ap_cell, True )
734 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
735 self.plist.append_column( self.ap_col )
736 # The signal column
737 self.sig_cell = gtk.CellRendererPixbuf()
738 self.signal_col = gtk.TreeViewColumn( "Signal" )
739 self.signal_col.pack_start( self.sig_cell, True )
740 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
741 self.plist.append_column( self.signal_col )
742 # The mode column
743 self.mode_cell = gtk.CellRendererText()
744 self.mode_col = gtk.TreeViewColumn( "Mode" )
745 self.mode_col.pack_start( self.mode_cell, True )
746 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
747 self.plist.append_column( self.mode_col )
748 # The protocol column
749 self.prot_cell = gtk.CellRendererText()
750 self.protocol_col = gtk.TreeViewColumn( "802.11" )
751 self.protocol_col.pack_start( self.prot_cell, True )
752 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
753 self.plist.append_column( self.protocol_col )
754 # The channel column
755 self.channel_cell = gtk.CellRendererText()
756 self.channel_col = gtk.TreeViewColumn( "Channel" )
757 self.channel_col.pack_start( self.channel_cell, True )
758 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
759 self.plist.append_column( self.channel_col )
760 # DnD Ordering
761 self.plist.set_reorderable( True )
762 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
763 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
764 # enable/disable buttons based on the selected network
765 self.selected_network = self.plist.get_selection()
766 self.selected_network.connect( 'changed', self.on_network_selection, None )
767 # the list scroll bar
768 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
769 sb.show()
770 self.plist.show()
771 # Add New button
772 self.new_button = gtk.Button( "_New" )
773 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
774 self.new_button.show()
775 # Add Configure button
776 self.edit_button = gtk.Button( "C_onfigure" )
777 self.edit_button.connect( 'clicked', self.edit_profile, None )
778 self.edit_button.show()
779 self.edit_button.set_sensitive(False)
780 # Add Delete button
781 self.delete_button = gtk.Button( "_Delete" )
782 self.delete_button.connect( 'clicked', self.delete_profile, None )
783 self.delete_button.show()
784 self.delete_button.set_sensitive(False)
785 # Add Connect button
786 self.connect_button = gtk.Button( "Co_nnect" )
787 self.connect_button.connect( 'clicked', self.connect_profile, None )
788 # Add Disconnect button
789 self.disconnect_button = gtk.Button( "D_isconnect" )
790 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
791 # lets add our widgets
792 rows = gtk.VBox( False, 3 )
793 net_list = gtk.HBox( False, 0 )
794 listcols = gtk.HBox( False, 0 )
795 prows = gtk.VBox( False, 0 )
796 # lets start packing
797 # the network list
798 net_list.pack_start( self.plist, True, True, 0 )
799 net_list.pack_start( sb, False, False, 0 )
800 # the rows level
801 rows.pack_start( net_list , True, True, 0 )
802 rows.pack_start( self.current_network, False, True, 0 )
803 # the list columns
804 listcols.pack_start( rows, True, True, 0 )
805 listcols.pack_start( prows, False, False, 5 )
806 # the list buttons
807 prows.pack_start( self.new_button, False, False, 2 )
808 prows.pack_start( self.edit_button, False, False, 2 )
809 prows.pack_start( self.delete_button, False, False, 2 )
810 prows.pack_end( self.connect_button, False, False, 2 )
811 prows.pack_end( self.disconnect_button, False, False, 2 )
813 self.window.action_area.pack_start( self.about_button )
814 self.window.action_area.pack_start( self.preferences_button )
815 self.window.action_area.pack_start( self.close_button )
817 rows.show()
818 prows.show()
819 listcols.show()
820 self.window.vbox.add( listcols )
821 self.window.vbox.set_spacing( 3 )
822 self.window.show_all()
824 # Now, immediately hide these two. The proper one will be
825 # displayed later, based on interface state. -BEF-
826 self.disconnect_button.hide()
827 self.connect_button.hide()
828 self.connect_button.set_sensitive(False)
830 # set up connection manager for later use
831 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
832 # set up status window for later use
833 self.status_window = StatusWindow( self )
834 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
836 # Add our known profiles in order
837 for ap in self.confFile.auto_profile_order:
838 ap = ap.strip()
839 self.access_points[ ap ] = self.confFile.get_profile( ap )
840 wep = None
841 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
842 self.pstore.append( [ self.access_points[ ap ]['essid'] + "\n" + self.access_points[ ap ]['bssid'], self.known_profile_icon, self.access_points[ ap ]['known'], self.access_points[ ap ]['available'], wep, self.signal_none_pb, self.access_points[ ap ]['mode'], self.access_points[ ap ]['protocol'], self.access_points[ ap ]['channel'] ] )
843 # This is the first run (or, at least, no config file was present), so pop up the preferences window
844 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
845 self.confFile.remove_option('DEFAULT', 'new_file')
846 self.edit_preferences(self.preferences_button)
848 # Begin running radar_window in Gtk event loop.
850 #Parameters:
852 # nothing
854 #Returns:
856 # nothing
857 def main( self ):
858 gtk.main()
860 # Quit application.
862 #Parameters:
864 # 'widget' -- gtk.Widget - The widget sending the event.
866 #Returns:
868 # nothing
869 def destroy( self, widget = None):
870 if self.status_window:
871 self.status_window.destroy()
872 gtk.main_quit()
874 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
876 #Parameters:
878 # 'widget' -- gtk.Widget - The widget sending the event.
880 # 'data' -- tuple - list of arbitrary arguments (not used)
882 #Returns:
884 # boolean -- always return False (i.e. do not propigate the signal which called)
885 def delete_event( self, widget, data = None ):
886 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
887 try:
888 self.commandQueue.put("exit", True)
889 except Queue.Full:
890 pass
891 # Save the preferred networks order
892 self.update_auto_profile_order()
893 self.destroy()
894 return False
896 # Updates the on-screen profiles list.
898 #Parameters:
900 # nothing
902 #Returns:
904 # boolean -- always return True
905 def update_plist_items( self ):
906 # Indicate to PyGtk that only one Gtk thread should run here
907 gtk.gdk.threads_enter()
908 # update the current ip and essid
909 # set the state of connect/disconnect buttons based on whether we have an IP address
910 if self.connection:
911 if self.connection.state:
912 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() ) )
913 self.connect_button.hide()
914 self.disconnect_button.show()
915 else:
916 self.current_network.set_text( "Not Connected." )
917 self.disconnect_button.hide()
918 self.connect_button.show()
920 while True:
921 # Get profiles scanned by iwlist
922 try:
923 profile = self.apQueue.get_nowait()
924 except Queue.Empty:
925 break
926 else:
927 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
928 wep = None
929 if prow_iter != None:
930 # the AP is in the list of APs on the screen
931 apname = make_section_name(profile['essid'], profile['bssid'])
932 if self.access_points.has_key(apname):
933 # This AP has been configured and is/should be stored in the config file
934 profile['known'] = self.access_points[apname]['known']
935 self.access_points[apname]['available'] = profile['available']
936 self.access_points[apname]['encrypted'] = profile['encrypted']
937 self.access_points[apname]['signal'] = profile['signal']
938 self.access_points[apname]['mode'] = profile['mode']
939 self.access_points[apname]['protocol'] = profile['protocol']
940 self.access_points[apname]['channel'] = profile['channel']
941 # Set the 'known' values; False is default, overridden to True by self.access_points
942 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
943 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
944 self.pstore.set_value(prow_iter, 3, profile['available'])
945 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
946 self.pstore.set_value(prow_iter, 4, wep)
947 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
948 self.pstore.set_value(prow_iter, 6, profile['mode'])
949 self.pstore.set_value(prow_iter, 7, profile['protocol'])
950 self.pstore.set_value(prow_iter, 8, profile['channel'])
951 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
952 #for val in self.pstore[prow_iter]:
953 #print val,
954 else:
955 # the AP is not in the list of APs on the screen
956 self.pstore.append( [ profile[ 'essid' ] + "\n" + profile['bssid'], self.pixbuf_from_known( profile['known'] ), profile['known'], profile['available'], wep, self.pixbuf_from_signal( profile['signal'] ), profile['mode'], profile['protocol'], profile['channel'] ] )
957 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
958 # Allow other Gtk threads to run
959 gtk.gdk.threads_leave()
960 #print "update_plist_items: Empty apQueue"
961 return True
963 # Return the proper icon for a value of known.
965 #Parameters:
967 # 'known' -- boolean - Whether the AP is known (i.e. configured)
969 #Returns:
971 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
972 def pixbuf_from_known( self, known ):
973 """ return the proper icon for value of known """
974 if known:
975 return self.known_profile_icon
976 else:
977 return self.unknown_profile_icon
979 # Return an icon indicating the signal level.
981 #Parameters:
983 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
985 #Returns:
987 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
988 def pixbuf_from_signal( self, signal ):
989 signal = int( signal )
990 # shift signal up by 80 to convert dBm scale to arbitrary scale
991 if signal < 0: signal = signal + 80
992 #print "signal level:", signal
993 if signal < 3:
994 return self.signal_none_pb
995 elif signal < 12:
996 return self.signal_low_pb
997 elif signal < 20:
998 return self.signal_barely_pb
999 elif signal < 35:
1000 return self.signal_ok_pb
1001 elif signal >= 35:
1002 return self.signal_best_pb
1003 else:
1004 return None
1006 # Return row which holds specified ESSID and BSSID.
1008 #Parameters:
1010 # 'essid' -- string - ESSID to match
1012 # 'bssid' -- string - BSSID to match
1014 #Returns:
1016 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1017 def get_row_by_ap( self, essid, bssid ):
1018 for row in self.pstore:
1019 if ( row[0] == essid + "\n" + bssid ):
1020 #print "matched:", row.iter, essid, bssid
1021 return row.iter
1022 return None
1024 # Enable/disable buttons based on the selected network.
1026 #Parameters:
1028 # 'widget' -- gtk.Widget - The widget sending the event.
1030 # 'data' -- tuple - list of arbitrary arguments (not used)
1032 #Returns:
1034 # nothing
1035 def on_network_selection( self, widget, data = None ):
1036 ( store, selected_iter ) = self.selected_network.get_selected()
1037 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1038 # if no networks are selected, disable all buttons except New
1039 # (this occurs after a drag-and-drop)
1040 if selected_iter == None:
1041 self.edit_button.set_sensitive(False)
1042 self.delete_button.set_sensitive(False)
1043 self.connect_button.set_sensitive(False)
1044 return
1045 # enable/disable buttons
1046 self.connect_button.set_sensitive(True)
1047 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1048 self.edit_button.set_sensitive(True)
1049 self.delete_button.set_sensitive(True)
1050 else:
1051 self.edit_button.set_sensitive(True)
1052 self.delete_button.set_sensitive(False)
1054 # Init and run the about dialog
1056 #Parameters:
1058 # 'widget' -- gtk.Widget - The widget sending the event.
1060 # 'data' -- tuple - list of arbitrary arguments (not used)
1062 #Returns:
1064 # nothing
1065 def show_about_info( self, widget, data=None ):
1066 about = about_dialog()
1067 about.run()
1068 about.destroy()
1070 # Init and run the preferences dialog
1072 #Parameters:
1074 # 'widget' -- gtk.Widget - The widget sending the event.
1076 # 'data' -- tuple - list of arbitrary arguments (not used)
1078 #Returns:
1080 # nothing
1081 def edit_preferences( self, widget, data=None ):
1082 # get raw strings from config file
1083 self.confFile.raw = True
1084 prefs = preferences_dialog( self, self.confFile )
1085 response = prefs.run()
1086 prefs.destroy()
1087 if response == int(gtk.RESPONSE_ACCEPT):
1088 prefs.save()
1089 # get cooked strings from config file
1090 self.confFile.raw = False
1092 # Respond to a request to create a new AP profile
1094 #Parameters:
1096 # 'widget' -- gtk.Widget - The widget sending the event.
1098 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1100 # 'data' -- tuple - list of arbitrary arguments (not used)
1102 #Returns:
1104 # boolean -- True if a profile was created and False if profile creation was canceled.
1105 def create_new_profile( self, widget, profile, data=None ):
1106 profile_editor = profile_dialog( self, profile )
1107 try:
1108 profile = profile_editor.run()
1109 except ValueError:
1110 dlg = gtk.MessageDialog( self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "Cannot save empty ESSID" )
1111 dlg.run()
1112 dlg.destroy()
1113 del dlg
1114 return False
1115 finally:
1116 profile_editor.destroy()
1117 if profile:
1118 apname = make_section_name( profile['essid'], profile['bssid'] )
1119 # Check that the ap does not exist already
1120 if apname in self.confFile.profiles():
1121 dlg = gtk.MessageDialog( self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "A profile for %s already exists" % (apname) )
1122 dlg.run()
1123 dlg.destroy()
1124 del dlg
1125 # try again
1126 self.access_points[ apname ] = profile
1127 self.confFile.set_section( apname, profile )
1128 # if it is not in the auto_profile_order add it
1129 if apname not in self.confFile.auto_profile_order:
1130 self.confFile.auto_profile_order.append(apname)
1131 # add to the store
1132 wep = None
1133 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1134 self.confFile.write()
1135 # Add AP to the list displayed to user
1136 try:
1137 self.apQueue.put_nowait( self.access_points[ apname ] )
1138 except Queue.Full:
1139 pass
1140 return True
1141 else:
1142 # Did not create new profile
1143 return False
1145 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1146 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1148 #Parameters:
1150 # 'widget' -- gtk.Widget - The widget sending the event.
1152 # 'data' -- tuple - list of arbitrary arguments (not used)
1154 #Returns:
1156 # nothing
1157 def edit_profile( self, widget, data=None ):
1158 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1159 if not selected_iter: return
1160 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1161 apname = make_section_name( row_start[0], row_start[1] )
1162 profile = self.confFile.get_profile( apname )
1163 if profile:
1164 profile_editor = profile_dialog( self, profile )
1165 try:
1166 profile = profile_editor.run()
1167 except ValueError:
1168 dlg = gtk.MessageDialog( self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "Cannot save empty ESSID" )
1169 dlg.run()
1170 dlg.destroy()
1171 del dlg
1172 return False
1173 finally:
1174 profile_editor.destroy()
1175 if profile:
1176 apname = make_section_name( profile['essid'], profile['bssid'] )
1177 self.access_points[ apname ] = profile
1178 self.confFile.set_section( apname, profile )
1179 self.confFile.write()
1180 else:
1181 profile = get_new_profile()
1182 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1183 self.create_new_profile( widget, profile, data )
1185 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1187 #Parameters:
1189 # 'widget' -- gtk.Widget - The widget sending the event.
1191 # 'data' -- tuple - list of arbitrary arguments (not used)
1193 #Returns:
1195 # nothing
1196 def delete_profile( self, widget, data=None ):
1197 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1198 if not selected_iter: return
1199 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1200 known = store.get_value( selected_iter, 1 )
1201 if not known: return
1202 dlg = gtk.MessageDialog(
1203 self.window,
1204 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1205 gtk.MESSAGE_QUESTION,
1206 gtk.BUTTONS_YES_NO,
1207 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1208 res = dlg.run()
1209 dlg.destroy()
1210 del dlg
1211 if res == gtk.RESPONSE_NO: return
1212 # Remove it
1213 apname = make_section_name( essid, bssid )
1214 del self.access_points[ apname ]
1215 self.confFile.remove_section( apname )
1216 self.logger.debug(apname)
1217 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1218 self.pstore.remove( selected_iter )
1219 # Let's save our current state
1220 self.update_auto_profile_order()
1221 self.confFile.write()
1223 # Respond to a request to connect to an AP.
1225 #Parameters:
1227 # 'widget' -- gtk.Widget - The widget sending the event.
1229 # 'profile' -- dictionary - The AP profile to which to connect.
1231 # 'data' -- tuple - list of arbitrary arguments (not used)
1233 #Returns:
1235 # nothing
1236 def connect_profile( self, widget, profile, data=None ):
1237 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1238 if not selected_iter: return
1239 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1240 known = store.get_value( selected_iter, 2 )
1241 if not known:
1242 if data != 'noconnect':
1243 dlg = gtk.MessageDialog(
1244 self.window,
1245 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1246 gtk.MESSAGE_QUESTION,
1247 gtk.BUTTONS_YES_NO,
1248 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1249 res = dlg.run()
1250 dlg.destroy()
1251 del dlg
1252 if res == gtk.RESPONSE_NO: return
1253 profile = get_new_profile()
1254 profile['essid'] = essid
1255 profile['bssid'] = bssid
1256 if not self.create_new_profile( widget, profile, data ):
1257 return
1258 apname = make_section_name( essid, bssid )
1259 self.connection.connect_to_network(self.access_points[apname], self.status_window)
1261 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1263 #Parameters:
1265 # 'widget' -- gtk.Widget - The widget sending the event.
1267 # 'data' -- tuple - list of arbitrary arguments (not used)
1269 #Returns:
1271 # nothing
1272 def disconnect_profile( self, widget, data=None ):
1273 if data == "cancel":
1274 self.status_window.update_message("Canceling connection...")
1275 if sys.modules.has_key("gtk"):
1276 while gtk.events_pending():
1277 gtk.main_iteration(False)
1278 sleep(1)
1279 self.connection.disconnect_interface()
1281 # Update the config file auto profile order from the on-screen order
1283 #Parameters:
1285 # 'widget' -- gtk.Widget - The widget sending the event.
1287 # 'data' -- tuple - list of arbitrary arguments (not used)
1289 # 'data2' -- tuple - list of arbitrary arguments (not used)
1291 #Returns:
1293 # nothing
1294 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1295 # recreate the auto_profile_order
1296 auto_profile_order = []
1297 piter = self.pstore.get_iter_first()
1298 while piter:
1299 # only if it's known
1300 if self.pstore.get_value( piter, 2 ) == True:
1301 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1302 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1303 piter = self.pstore.iter_next( piter )
1304 self.confFile.auto_profile_order = auto_profile_order
1305 self.confFile.write()
1308 # Button to allow user to choose a file and put value into specified gtk.Entry
1309 class file_browse_button(gtk.Button):
1310 # Create a button to simulate a File/Open
1312 #Parameters:
1314 # 'parent' -- gtk.Object -- Usually, the calling window.
1316 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1318 #Returns:
1320 # file_browse_button instance
1321 def __init__( self, parent, entry ):
1322 self.parent_window = parent
1323 self.entry = entry
1324 gtk.Button.__init__(self, "Browse", None)
1325 #self.
1326 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)
1327 self.connect("clicked", self.browse_files)
1329 # Show filechooser dialog and get user selection
1331 #Parameters:
1333 # 'widget' -- gtk.Widget -- The widget sending the event.
1335 #Returns:
1337 # nothing
1339 #NOTES:
1341 # updates entry value
1343 def browse_files( self, widget ):
1344 self.browser_dialog.set_filename(self.entry.get_text())
1345 self.browser_dialog.run()
1346 self.entry.set_text(self.browser_dialog.get_filename())
1347 self.browser_dialog.destroy()
1350 # Simple dialog to report an error to the user.
1351 class ErrorDialog:
1352 # Create a new ErrorDialog.
1354 #Parameters:
1356 # 'parent' -- gtk.Object - Usually, the calling window.
1358 # 'message' -- string - The message to display to the user.
1360 #Returns:
1362 # ErrorDialog instance
1363 def __init__( self, parent, message ):
1364 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1365 dialog.run()
1366 dialog.destroy()
1367 del dialog
1370 # The preferences dialog. Edits non-profile sections of the config file.
1371 class preferences_dialog:
1372 # Create a new preferences_dialog.
1374 #Parameters:
1376 # 'parent' -- gtk.Object - Usually, the calling window.
1378 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1380 #Returns:
1382 # preferences_dialog instance
1383 def __init__( self, parent, confFile ):
1384 global wifi_radar_icon
1385 self.parent = parent
1386 self.confFile = confFile
1387 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1388 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1389 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1390 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1391 self.dialog.set_icon( icon )
1392 self.dialog.set_resizable( True )
1393 self.dialog.set_transient_for( self.parent.window )
1394 self.tooltips = gtk.Tooltips()
1396 # set up preferences widgets
1398 # build everything in a tabbed notebook
1399 self.prefs_notebook = gtk.Notebook()
1401 ### General tab
1402 self.general_page = gtk.VBox()
1403 # auto detect wireless device
1404 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1406 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1408 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1409 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1410 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1411 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1413 # network interface selecter
1414 self.w_interface = gtk.combo_box_entry_new_text()
1415 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1416 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1417 for device in wireless_devices:
1418 if device != self.confFile.get_opt('DEFAULT.interface'):
1419 self.w_interface.append_text(device)
1420 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1421 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1422 self.w_interface.set_active(0)
1423 self.w_interface_label = gtk.Label("Wireless device")
1424 self.w_hbox1 = gtk.HBox(False, 0)
1425 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1426 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1427 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1428 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1430 # scan timeout (spin button of integers from 1 to 100)
1431 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1432 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1433 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1434 self.w_scan_timeout.set_numeric(True)
1435 self.w_scan_timeout.set_snap_to_ticks(True)
1436 self.w_scan_timeout.set_wrap(False)
1437 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1438 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1439 self.w_hbox2 = gtk.HBox(False, 0)
1440 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1441 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1442 self.general_page.pack_start(self.w_hbox2, False, False, 5)
1444 # speak up
1445 self.w_speak_up = gtk.CheckButton("Use speak-up")
1446 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1447 self.w_speak_up.connect("toggled", self.toggle_speak)
1448 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1449 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1451 # speak up command
1452 self.w_speak_cmd = gtk.Entry()
1453 self.w_speak_cmd.set_width_chars(16)
1454 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1455 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1456 self.w_speak_cmd_label = gtk.Label("Speak Command")
1457 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1458 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1459 self.w_hbox3 = gtk.HBox(False, 0)
1460 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1461 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1462 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1463 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1464 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1466 # commit required
1467 self.w_commit_required = gtk.CheckButton("Commit required")
1468 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1469 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1470 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1472 # ifup required
1473 self.w_ifup_required = gtk.CheckButton("Ifup required")
1474 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1475 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1476 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1478 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1479 ### End of General tab
1481 ### Advanced tab
1482 # table to use for layout of following command configurations
1483 self.cmds_table = gtk.Table()
1485 # ifconfig command
1486 self.ifconfig_cmd = gtk.Entry()
1487 self.ifconfig_cmd.set_width_chars(32)
1488 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1489 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1490 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1491 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1492 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1493 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1494 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1495 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1497 # iwconfig command
1498 self.iwconfig_cmd = gtk.Entry()
1499 self.iwconfig_cmd.set_width_chars(32)
1500 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1501 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1502 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1503 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1504 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1505 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1506 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1507 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1509 # iwlist command
1510 self.iwlist_cmd = gtk.Entry()
1511 self.iwlist_cmd.set_width_chars(32)
1512 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1513 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1514 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1515 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1516 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1517 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1518 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1519 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1521 # route command
1522 self.route_cmd = gtk.Entry()
1523 self.route_cmd.set_width_chars(32)
1524 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1525 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1526 self.route_cmd_label = gtk.Label("Network route configure command")
1527 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1528 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1529 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1530 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1531 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1533 # log file
1534 self.logfile_entry = gtk.Entry()
1535 self.logfile_entry.set_width_chars(32)
1536 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1537 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1538 self.logfile_label = gtk.Label("Log file")
1539 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1540 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1541 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, True, False, 5, 0)
1542 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, True, False, 0, 0)
1543 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, False, False, 0, 0)
1545 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1546 ### End of Advanced tab
1548 ### DHCP tab
1549 # table to use for layout of DHCP prefs
1550 self.dhcp_table = gtk.Table()
1552 self.dhcp_cmd = gtk.Entry()
1553 self.dhcp_cmd.set_width_chars(32)
1554 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1555 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1556 self.dhcp_cmd_label = gtk.Label("Command")
1557 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1558 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1559 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1560 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1562 self.dhcp_args = gtk.Entry()
1563 self.dhcp_args.set_width_chars(32)
1564 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1565 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1566 self.dhcp_args_label = gtk.Label("Arguments")
1567 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1568 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1570 self.dhcp_kill_args = gtk.Entry()
1571 self.dhcp_kill_args.set_width_chars(32)
1572 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1573 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1574 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1575 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1576 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1578 self.dhcp_timeout = gtk.Entry()
1579 self.dhcp_timeout.set_width_chars(32)
1580 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1581 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1582 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1583 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1584 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1586 self.dhcp_pidfile = gtk.Entry()
1587 self.dhcp_pidfile.set_width_chars(32)
1588 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1589 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1590 self.dhcp_pidfile_label = gtk.Label("PID file")
1591 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1592 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1594 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1595 ### End of DHCP tab
1597 ### WPA tab
1598 # table to use for layout of DHCP prefs
1599 self.wpa_table = gtk.Table()
1601 self.wpa_cmd = gtk.Entry()
1602 self.wpa_cmd.set_width_chars(32)
1603 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1604 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1605 self.wpa_cmd_label = gtk.Label("Command")
1606 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1607 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1608 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1609 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1611 self.wpa_args = gtk.Entry()
1612 self.wpa_args.set_width_chars(32)
1613 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1614 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1615 self.wpa_args_label = gtk.Label("Arguments")
1616 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1617 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1619 self.wpa_kill_args = gtk.Entry()
1620 self.wpa_kill_args.set_width_chars(32)
1621 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1622 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1623 self.wpa_kill_args_label = gtk.Label("Kill command")
1624 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1625 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1627 self.wpa_config = gtk.Entry()
1628 self.wpa_config.set_width_chars(32)
1629 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1630 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1631 self.wpa_config_label = gtk.Label("Configuration file")
1632 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1633 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1635 self.wpa_driver = gtk.Entry()
1636 self.wpa_driver.set_width_chars(32)
1637 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1638 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1639 self.wpa_driver_label = gtk.Label("Driver")
1640 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1641 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1643 self.wpa_pidfile = gtk.Entry()
1644 self.wpa_pidfile.set_width_chars(32)
1645 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1646 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1647 self.wpa_pidfile_label = gtk.Label("PID file")
1648 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1649 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1651 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1652 ### End of WPA tab
1654 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1656 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1658 #Parameters:
1660 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1662 # 'data' -- tuple - list of arbitrary arguments (not used)
1664 #Returns:
1666 # nothing
1667 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1668 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1670 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1672 #Parameters:
1674 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1676 # 'data' -- tuple - list of arbitrary arguments (not used)
1678 #Returns:
1680 # nothing
1681 def toggle_speak(self, speak_toggle, data=None):
1682 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1683 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1685 # Display preferences dialog and operate until canceled or okayed.
1687 #Parameters:
1689 # nothing
1691 #Returns:
1693 # integer -- gtk response ID
1694 def run(self):
1695 self.dialog.show_all()
1696 return self.dialog.run()
1698 # Write updated values to config file.
1700 #Parameters:
1702 # nothing
1704 #Returns:
1706 # nothing
1707 def save(self):
1708 if self.w_auto_detect.get_active():
1709 set_network_device("auto_detect")
1710 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1711 else:
1712 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1713 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1714 self.confFile.set_opt('DEFAULT.interface', interface)
1715 set_network_device(interface)
1716 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1717 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1718 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1719 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1720 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1721 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1722 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1723 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1724 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1725 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1726 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1727 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1728 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1729 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1730 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1731 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1732 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1733 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1734 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1735 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1736 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1737 self.confFile.write()
1739 # Remove preferences window.
1741 #Parameters:
1743 # nothing
1745 #Returns:
1747 # nothing
1748 def destroy(self):
1749 self.dialog.destroy()
1750 del self.dialog
1753 # Edit and return an AP profile.
1754 class profile_dialog:
1755 # Create a new profile_dialog.
1757 #Parameters:
1759 # 'parent' -- gtk.Object - Usually, the calling window.
1761 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1763 #Returns:
1765 # profile_dialog instance
1766 def __init__( self, parent, profile ):
1767 global wifi_radar_icon
1768 self.parent = parent
1769 self.profile = profile
1770 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1771 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1772 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1773 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1774 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1775 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1776 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1777 self.dialog.set_icon( icon )
1778 self.dialog.set_resizable( False )
1779 self.dialog.set_transient_for( self.parent.window )
1780 #self.dialog.set_size_request( 400, 400 )
1781 #################
1782 self.tooltips = gtk.Tooltips()
1784 essid_table = gtk.Table( 1, 2, False )
1785 essid_table.set_row_spacings( 3 )
1786 essid_table.set_col_spacings( 3 )
1787 # The essid labels
1788 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1789 # The essid textboxes
1790 self.essid_entry = gtk.Entry( 32 )
1791 self.essid_entry.set_text( self.profile['essid'] )
1792 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1793 # Add the essid table to the dialog
1794 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1795 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
1797 bssid_table = gtk.Table( 1, 2, False )
1798 bssid_table.set_row_spacings( 3 )
1799 bssid_table.set_col_spacings( 3 )
1800 # The bssid labels
1801 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1802 # The bssid textboxes
1803 self.bssid_entry = gtk.Entry( 32 )
1804 self.bssid_entry.set_text( self.profile['bssid'] )
1805 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1806 #self.key = gtk.Entry( 32 )
1807 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1808 # Add the bssid table to the dialog
1809 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1810 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
1812 # create the WiFi expander
1813 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1814 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1815 wifi_table = gtk.Table( 4, 2, False )
1816 wifi_table.set_row_spacings( 3 )
1817 wifi_table.set_col_spacings( 3 )
1818 # The WiFi labels
1819 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1820 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1821 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1822 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1823 # The WiFi text boxes
1824 self.mode_combo = gtk.combo_box_new_text()
1825 for mode in self.WIFI_MODES:
1826 self.mode_combo.append_text( mode )
1827 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1828 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1829 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
1830 self.channel_combo = gtk.combo_box_new_text()
1831 for channel in self.WIFI_CHANNELS:
1832 self.channel_combo.append_text( channel )
1833 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1834 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1835 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
1837 self.key_entry = gtk.Entry( 64 )
1838 self.key_entry.set_text( self.profile['key'] )
1839 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1840 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
1842 self.security_combo = gtk.combo_box_new_text()
1843 for security in self.WIFI_SECURITY:
1844 self.security_combo.append_text( security )
1845 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1846 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1847 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
1848 # Add the wifi table to the expander
1849 self.wifi_expander.add( wifi_table )
1850 # Add the expander to the dialog
1851 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1853 # create the wpa expander
1854 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1855 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1856 wpa_table = gtk.Table( 1, 2, False )
1857 wpa_table.set_row_spacings( 3 )
1858 wpa_table.set_col_spacings( 3 )
1859 # The labels
1860 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1861 # The text boxes
1862 self.wpa_driver_entry = gtk.Entry()
1863 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1864 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1865 # Add the wpa table to the expander
1866 self.wpa_expander.add( wpa_table )
1867 # Add the expander to the dialog
1868 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1870 # create the dhcp expander
1871 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1872 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1873 ip_table = gtk.Table( 6, 2, False )
1874 ip_table.set_row_spacings( 3 )
1875 ip_table.set_col_spacings( 3 )
1876 # The IP labels
1877 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1878 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1879 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1880 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1881 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1882 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1883 # The IP text boxes
1884 self.ip_entry = gtk.Entry( 15 )
1885 self.ip_entry.set_text( self.profile['ip'] )
1886 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1887 self.netmask_entry = gtk.Entry( 15 )
1888 self.netmask_entry.set_text( self.profile['netmask'] )
1889 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1890 self.gw_entry = gtk.Entry( 15 )
1891 self.gw_entry.set_text( self.profile['gateway'] )
1892 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1893 self.domain_entry = gtk.Entry( 32 )
1894 self.domain_entry.set_text( self.profile['domain'] )
1895 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1896 self.dns1_entry = gtk.Entry( 15 )
1897 self.dns1_entry.set_text( self.profile['dns1'] )
1898 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1899 self.dns2_entry = gtk.Entry( 15 )
1900 self.dns2_entry.set_text( self.profile['dns2'] )
1901 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1902 # Add the ip table to the expander
1903 self.dhcp_expander.add( ip_table )
1904 # Add the expander to the dialog
1905 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1907 # create the connection-building postpre expander
1908 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1909 con_pp_table = gtk.Table( 2, 2, False )
1910 con_pp_table.set_row_spacings( 3 )
1911 con_pp_table.set_col_spacings( 3 )
1912 # The labels
1913 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1914 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1915 # The text boxes
1916 self.con_prescript_entry = gtk.Entry()
1917 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1918 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1919 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
1920 self.con_postscript_entry = gtk.Entry()
1921 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1922 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1923 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
1924 # Add the pp table to the expander
1925 self.con_pp_expander.add( con_pp_table )
1926 # Add the expander to the dialog
1927 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1929 # create the disconnection postpre expander
1930 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1931 dis_pp_table = gtk.Table( 2, 2, False )
1932 dis_pp_table.set_row_spacings( 3 )
1933 dis_pp_table.set_col_spacings( 3 )
1934 # The labels
1935 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1936 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1937 # The text boxes
1938 self.dis_prescript_entry = gtk.Entry()
1939 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1940 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1941 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
1942 self.dis_postscript_entry = gtk.Entry()
1943 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1944 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1945 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
1946 # Add the pp table to the expander
1947 self.dis_pp_expander.add( dis_pp_table )
1948 # Add the expander to the dialog
1949 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1951 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1953 #Parameters:
1955 # nothing
1957 #Returns:
1959 # dictionary or None -- a profile, or None on cancel
1961 #NOTES:
1963 # Raises ValueError if an attempt is made to save an ESSID with no name.
1964 def run( self ):
1965 self.dialog.show_all()
1966 if self.dialog.run():
1967 if self.essid_entry.get_text().strip() == "":
1968 raise ValueError
1969 self.profile['known'] = True
1970 self.profile['essid'] = self.essid_entry.get_text().strip()
1971 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1972 self.profile['key'] = self.key_entry.get_text().strip()
1973 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1974 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1975 self.profile['encrypted'] = ( self.profile['security'] != '' )
1976 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1977 self.profile['protocol'] = 'g'
1978 self.profile['available'] = ( self.profile['signal'] > 0 )
1979 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1980 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1981 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1982 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1983 # wpa
1984 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1985 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1986 # dhcp
1987 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1988 self.profile['ip'] = self.ip_entry.get_text().strip()
1989 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1990 self.profile['gateway'] = self.gw_entry.get_text().strip()
1991 self.profile['domain'] = self.domain_entry.get_text().strip()
1992 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1993 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1994 return self.profile
1995 return None
1997 # Remove profile dialog.
1999 #Parameters:
2001 # nothing
2003 #Returns:
2005 # nothing
2006 def destroy( self ):
2007 self.dialog.destroy()
2008 del self.dialog
2010 # Respond to expanding/hiding IP segment.
2012 #Parameters:
2014 # 'widget' -- gtk.Widget - The widget sending the event.
2016 # 'data' -- tuple - List of arbitrary arguments (not used)
2018 #Returns:
2020 # nothing
2021 def toggle_use_dhcp( self, widget, data = None ):
2022 expanded = self.dhcp_expander.get_expanded()
2023 if expanded:
2024 self.dhcp_expander.set_label( USE_IP_LABEL )
2025 else:
2026 self.dhcp_expander.set_label( USE_DHCP_LABEL )
2028 # Respond to expanding/hiding WPA segment.
2030 #Parameters:
2032 # 'widget' -- gtk.Widget - The widget sending the event.
2034 # 'data' -- tuple - List of arbitrary arguments (not used)
2036 #Returns:
2038 # nothing
2039 def toggle_use_wpa( self, widget, data = None ):
2040 expanded = self.wpa_expander.get_expanded()
2041 if expanded:
2042 self.wpa_expander.set_label( USE_WPA_LABEL )
2043 else:
2044 self.wpa_expander.set_label( NO_WPA_LABEL )
2046 # Return the index where item matches a cell in array.
2048 #Parameters:
2050 # 'item' -- string - Item to find in array
2052 # 'array' -- list - List in which to find match.
2054 #Returns:
2056 # integer - 0 (no match) or higher (index of match)
2057 def get_array_index( self, item, array ):
2058 try:
2059 return array.index( item.strip() )
2060 except:
2061 pass
2062 return 0
2064 # Return the value in array[ index ]
2066 #Parameters:
2068 # 'index' -- integer - The index to look up.
2070 # 'array' -- list - List in which to look up value.
2072 #Returns:
2074 # string -- empty string (no match) or looked up value
2075 def get_array_item( self, index, array ):
2076 try:
2077 return array[ index ]
2078 except:
2079 pass
2080 return ''
2083 # A simple class for putting up a "Please wait" dialog so the user
2084 # doesn't think we've forgotten about them. Implements the status interface.
2085 class StatusWindow:
2086 # Create a new StatusWindow.
2088 #Parameters:
2090 # 'parent' -- gtk.Object - Usually, the calling window.
2092 #Returns:
2094 # StatusWindow instance
2096 #NOTE:
2098 # Sample implementation of status interface. Status interface
2099 #requires .show(), .update_message(message), and .hide() methods.
2100 def __init__( self, parent ):
2101 global wifi_radar_icon
2102 self.parent = parent
2103 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2104 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2105 self.dialog.set_icon( icon )
2106 self.lbl = gtk.Label("Please wait...")
2107 self.bar = gtk.ProgressBar()
2108 self.dialog.vbox.pack_start(self.lbl)
2109 self.dialog.vbox.pack_start(self.bar)
2110 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2111 self.timer = None
2113 # Change the message displayed to the user.
2115 #Parameters:
2117 # 'message' -- string - The message to show to the user.
2119 #Returns:
2121 # nothing
2122 def update_message( self, message ):
2123 self.lbl.set_text(message)
2125 # Update the StatusWindow progress bar.
2127 #Parameters:
2129 # nothing
2131 #Returns:
2133 # True -- always return True
2134 def update_window( self ):
2135 self.bar.pulse()
2136 return True
2138 # Display and operate the StatusWindow.
2140 #Parameters:
2142 # nothing
2144 #Returns:
2146 # nothing
2147 def run( self ):
2148 pass
2150 # Show all the widgets of the StatusWindow.
2152 #Parameters:
2154 # nothing
2156 #Returns:
2158 # nothing
2159 def show( self ):
2160 self.dialog.show_all()
2161 self.timer = gobject.timeout_add(250, self.update_window)
2162 return False
2164 # Hide all the widgets of the StatusWindow.
2166 #Parameters:
2168 # nothing
2170 #Returns:
2172 # nothing
2173 def hide( self ):
2174 if self.timer:
2175 gobject.source_remove(self.timer)
2176 self.timer = None
2177 self.dialog.hide_all()
2178 return False
2180 # Remove the StatusWindow.
2182 #Parameters:
2184 # nothing
2186 #Returns:
2188 # nothing
2189 def destroy( self ):
2190 if self.timer:
2191 gobject.source_remove(self.timer)
2192 self.dialog.destroy()
2193 del self.dialog
2196 # Manage a GTK About Dialog
2197 class about_dialog(gtk.AboutDialog):
2198 # Subclass GTK AboutDialog
2200 #Parameters:
2202 # nothing
2204 #Returns:
2206 # nothing
2207 def __init__( self ):
2208 global wifi_radar_icon
2210 gtk.AboutDialog.__init__(self)
2211 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", u"Ante Karamati\xc4\x87", "Richard Monk", "Brouard Nicolas", "Kevin Otte", "Nathanael Rebsch", "Patrick Winnertz"])
2212 self.set_comments("WiFi connection manager")
2213 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2214 self.set_documenters(["Gary Case"])
2215 license = """
2216 This program is free software; you can redistribute it and/or modify
2217 it under the terms of the GNU General Public License as published by
2218 the Free Software Foundation; either version 2 of the License, or
2219 (at your option) any later version.
2221 This program is distributed in the hope that it will be useful,
2222 but WITHOUT ANY WARRANTY; without even the implied warranty of
2223 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2224 GNU General Public License for more details.
2226 You should have received a copy of the GNU General Public License
2227 along with this program; if not, write to the Free Software
2228 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2229 self.set_license(license)
2230 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2231 self.set_logo(logo)
2232 self.set_name("WiFi Radar")
2233 self.set_version(WIFI_RADAR_VERSION)
2234 self.set_website("http://wifi-radar.berlios.de")
2238 # Manage the configuration for the application, including reading and writing the config from/to a file.
2239 class ConfigFile(ConfigParser.SafeConfigParser):
2240 # Create a new ConfigFile.
2242 #Parameters:
2244 # 'filename' -- string - The configuration file's name.
2246 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2248 #Returns:
2250 # ConfigFile instance
2251 def __init__( self, filename, defaults, raw=False ):
2252 self.filename = filename
2253 self.raw = raw
2254 self.auto_profile_order = []
2255 ConfigParser.SafeConfigParser.__init__(self, defaults)
2257 # Set the contents of a section to values from a dictionary.
2259 #Parameters:
2261 # 'section_name' -- string - Configuration file section.
2263 # 'section_dict' -- dictionary - Values to add to section.
2265 #Returns:
2267 # nothing
2268 def set_section( self, section_name, section_dict ):
2269 try:
2270 self.add_section(section_name)
2271 except ConfigParser.DuplicateSectionError:
2272 pass
2273 for key in section_dict.keys():
2274 if type(section_dict[key]) == BooleanType:
2275 self.set_bool_opt(section_name + "." + key, section_dict[key])
2276 elif type(section_dict[key]) == IntType:
2277 self.set_int_opt(section_name + "." + key, section_dict[key])
2278 elif type(section_dict[key]) == FloatType:
2279 self.set_float_opt(section_name + "." + key, section_dict[key])
2280 else:
2281 self.set_opt(section_name + "." + key, section_dict[key])
2283 # Return the profile recorded in the specified section.
2285 #Parameters:
2287 # 'section_name' -- string - Configuration file section.
2289 #Returns:
2291 # dictionary or None - The specified profile or None if not found
2292 def get_profile( self, section_name ):
2293 if section_name in self.profiles():
2294 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' ]
2295 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
2296 int_types = [ 'signal' ]
2297 profile = {}
2298 for option in bool_types:
2299 profile[option] = self.get_opt_as_bool( section_name + "." + option )
2300 for option in int_types:
2301 profile[option] = self.get_opt_as_int( section_name + "." + option )
2302 for option in str_types:
2303 profile[option] = self.get_opt( section_name + "." + option )
2304 return profile
2305 return None
2307 # Get a config option and handle exceptions.
2309 #Parameters:
2311 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2312 # period and the option key. (E.g. "DEFAULT.interface")
2314 #Returns:
2316 # string or None - option value as string or None on failure
2317 def get_opt( self, option_path ):
2318 #print "ConfigFile.get_opt: ", option_path
2319 (section, option) = option_path.split('.')
2320 try:
2321 return self.get(section, option, self.raw)
2322 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2323 return None
2325 # Get a config option and return as a boolean type.
2327 #Parameters:
2329 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2330 # period and the option key. (E.g. "DEFAULT.interface")
2332 #Returns:
2334 # boolean - option value as boolean
2335 def get_opt_as_bool( self, option_path ):
2336 option = self.get_opt(option_path)
2337 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2338 return option
2339 if option == 'True':
2340 return True
2341 if option == 'False':
2342 return False
2343 raise ValueError, 'boolean option was not True or False'
2345 # Get a config option and return as an integer type.
2347 #Parameters:
2349 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2350 # period and the option key. (E.g. "DEFAULT.interface")
2352 #Returns:
2354 # integer- option value as integer
2355 def get_opt_as_int( self, option_path ):
2356 return int(float(self.get_opt(option_path)))
2358 # Convert boolean type to string and set config option.
2360 #Parameters:
2362 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2363 # period and the option key. (E.g. "DEFAULT.interface")
2365 # 'value' -- boolean - Value to set.
2367 #Returns:
2369 # nothing
2370 def set_bool_opt( self, option_path, value ):
2371 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2372 value == 'True'
2373 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2374 value == 'False'
2375 else:
2376 raise ValueError, 'cannot convert value to string'
2377 self.set_opt(option_path, repr(value))
2379 # Convert integer type to string and set config option.
2381 #Parameters:
2383 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2384 # period and the option key. (E.g. "DEFAULT.interface")
2386 # 'value' -- integer - Value to set.
2388 #Returns:
2390 # nothing
2391 def set_int_opt( self, option_path, value ):
2392 if not isinstance(value, IntType):
2393 raise ValueError, 'value is not an integer'
2394 self.set_opt(option_path, repr(value))
2396 # Convert float type to string and set config option.
2398 #Parameters:
2400 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2401 # period and the option key. (E.g. "DEFAULT.interface")
2403 # 'value' -- float - Value to set.
2405 #Returns:
2407 # nothing
2408 def set_float_opt( self, option_path, value ):
2409 if not isinstance(value, FloatType):
2410 raise ValueError, 'value is not a float'
2411 self.set_opt(option_path, repr(int(value)))
2413 # Set a config option while handling exceptions.
2415 #Parameters:
2417 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2418 # period and the option key. (E.g. "DEFAULT.interface")
2420 # 'value' -- string - Value to set.
2422 #Returns:
2424 # nothing
2425 def set_opt( self, option_path, value ):
2426 (section, option) = option_path.split('.')
2427 try:
2428 self.set(section, option, value)
2429 except ConfigParser.NoSectionError:
2430 self.add_section(section)
2431 self.set_opt(option_path, value)
2433 # Return a list of the section names which denote AP profiles.
2435 #Parameters:
2437 # nothing
2439 #Returns:
2441 # list - profile names
2442 def profiles( self ):
2443 profile_list = []
2444 for section in self.sections():
2445 if ':' in section:
2446 profile_list.append(section)
2447 return profile_list
2449 # Read configuration file from disk into instance variables.
2451 #Parameters:
2453 # nothing
2455 #Returns:
2457 # nothing
2458 def read( self ):
2459 fp = open( self.filename, "r" )
2460 self.readfp(fp)
2461 # convert the auto_profile_order to a list for ordering
2462 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2463 for ap in self.profiles():
2464 self.set_bool_opt( ap + '.known', True)
2465 if ap in self.auto_profile_order: continue
2466 self.auto_profile_order.append( ap )
2467 fp.close()
2469 # Write configuration file to disk from instance variables. Copied from
2470 # ConfigParser and modified to write options in alphabetical order.
2472 #Parameters:
2474 # nothing
2476 #Returns:
2478 # nothing
2479 def write( self ):
2480 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2481 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2482 fp = open( self.filename, "w" )
2483 # write DEFAULT section first
2484 if self._defaults:
2485 fp.write("[DEFAULT]\n")
2486 for key in sorted(self._defaults.keys()):
2487 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2488 fp.write("\n")
2489 # write non-profile sections first
2490 for section in self._sections:
2491 if section not in self.profiles():
2492 fp.write("[%s]\n" % section)
2493 for key in sorted(self._sections[section].keys()):
2494 if key != "__name__":
2495 fp.write("%s = %s\n" %
2496 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2497 fp.write("\n")
2498 # write profile sections
2499 for section in self._sections:
2500 if section in self.profiles():
2501 fp.write("[%s]\n" % section)
2502 for key in sorted(self._sections[section].keys()):
2503 if key != "__name__":
2504 fp.write("%s = %s\n" %
2505 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2506 fp.write("\n")
2507 fp.close()
2511 # Load our conf file and known profiles
2512 # Defaults, these may get overridden by values found in the conf file.
2513 config_defaults = { # The network interface you use.
2514 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2515 'interface': "auto_detect",
2516 # How long should the scan for access points last?
2517 'scan_timeout': '5',
2518 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2519 # Set the speak_up option to false if you do not have or want this.
2520 'speak_command': '/usr/bin/say',
2521 # Should I speak up when connecting to a network? (If you have a speech command)
2522 'speak_up': 'False',
2523 # You may set this to true for cards that require a "commit" command with iwconfig
2524 'commit_required': 'False',
2525 # You may set this to true for cards that require the interface to be brought up first
2526 'ifup_required': 'False',
2527 # set the location of the log file
2528 'logfile': './wifi-radar.log',
2529 # Set the location of several important programs
2530 'iwlist_command': '/sbin/iwlist',
2531 'iwconfig_command': '/sbin/iwconfig',
2532 'ifconfig_command': '/sbin/ifconfig',
2533 'route_command': '/sbin/route',
2534 'auto_profile_order': '[]',
2535 'version': WIFI_RADAR_VERSION }
2537 config_dhcp = { # DHCP client
2538 'command': 'dhcpcd',
2539 # How long to wait for an IP addr from DHCP server
2540 'timeout': '30',
2541 # Arguments to use with DHCP client on connect
2542 'args': '-D -o -i dhcp_client -t %(timeout)s',
2543 # Argument to use with DHCP client on disconnect
2544 'kill_args': '-k',
2545 # The file where DHCP client PID is written
2546 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2548 config_wpa = { # WPA Supplicant
2549 'command': '/usr/sbin/wpa_supplicant',
2550 # Arguments to use with WPA Supplicant on connect
2551 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2552 # Arguments to use with WPA Supplicant on disconnect
2553 'kill_command': '',
2554 # Where the WPA Supplicant config file can be found
2555 'configuration': '/etc/wpa_supplicant.conf',
2556 # Driver to use with WPA Supplicant
2557 'driver': 'wext',
2558 # The file where WPA Supplicant PID is written
2559 'pidfile': '/var/run/wpa_supplicant.pid' }
2561 # initialize config, with defaults
2562 confFile = ConfigFile(CONF_FILE, config_defaults)
2563 confFile.set_section("DHCP", config_dhcp)
2564 confFile.set_section("WPA", config_wpa)
2566 if not os.path.isfile( CONF_FILE ):
2567 confFile.set_bool_opt('DEFAULT.new_file', True)
2568 else:
2569 if not os.access(CONF_FILE, os.R_OK):
2570 print "Can't open " + CONF_FILE + "."
2571 print "Are you root?"
2572 sys.exit()
2573 confFile.read()
2576 ####################################################################################################
2577 # Embedded Images
2578 wifi_radar_icon = [ ""
2579 "GdkP"
2580 "\0\0\22""7"
2581 "\2\1\0\2"
2582 "\0\0\1\214"
2583 "\0\0\0c"
2584 "\0\0\0O"
2585 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2586 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2587 "\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"
2588 "\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"
2589 "\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"
2590 "\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"
2591 "\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"
2592 "\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"
2593 "\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"
2594 "\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"
2595 "\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"
2596 "\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"
2597 "\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"
2598 "\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"
2599 "\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"
2600 "\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"
2601 "\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"
2602 "\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"
2603 "\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"
2604 "\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"
2605 "\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"
2606 "\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"
2607 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2608 "\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"
2609 "\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"
2610 "\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"
2611 "\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"
2612 "\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"
2613 "\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"
2614 "\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"
2615 "\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"
2616 "\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"
2617 "\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"
2618 "\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"
2619 "\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"
2620 "\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"
2621 "\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"
2622 "\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"
2623 "\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"
2624 "\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"
2625 "\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"
2626 "\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"
2627 "\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"
2628 "\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"
2629 "\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"
2630 "\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"
2631 "\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"
2632 "\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"
2633 "\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"
2634 "\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"
2635 "\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"
2636 "\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"
2637 "\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"
2638 "\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"
2639 "\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"
2640 "\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"
2641 "\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"
2642 "\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"
2643 "\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"
2644 "\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"
2645 "\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"
2646 "\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"
2647 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2648 "\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"
2649 "\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"
2650 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2651 "\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"
2652 "\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"
2653 "\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"
2654 "\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"
2655 "\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"
2656 "\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"
2657 "\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"
2658 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2659 "\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"
2660 "\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"
2661 "\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"
2662 "\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"
2663 "\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"
2664 "\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"
2665 "|\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"
2666 "\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"
2667 "\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"
2668 "\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"
2669 "\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"
2670 "\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"
2671 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2672 "\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"
2673 "\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"
2674 "\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"
2675 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2676 "\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"
2677 "\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"
2678 "\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"
2679 "\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"
2680 "\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"
2681 "\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"
2682 "\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"
2683 "\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"
2684 "\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"
2685 "\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"
2686 "\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"
2687 "\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"
2688 "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"
2689 "\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"
2690 "\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"
2691 "\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"
2692 "\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"
2693 "\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"
2694 "\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"
2695 "\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"
2696 "\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"
2697 "\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"
2698 "\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"
2699 "\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"
2700 "\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"
2701 "\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"
2702 "\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"
2703 "\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|"
2704 "\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"
2705 "\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"
2706 "\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"
2707 "\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"
2708 "\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"
2709 "\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"
2710 "\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"
2711 "\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"
2712 "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"
2713 "\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"
2714 "\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"
2715 "\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"
2716 "\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"
2717 "\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"
2718 "\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"
2719 "\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"
2720 "\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"
2721 "\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"
2722 "\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"
2723 "\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"
2724 "\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"
2725 "\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"
2726 "\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"
2727 "\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"
2728 "\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"
2729 "\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"
2730 "\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"
2731 "\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"
2732 "\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"
2733 "\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"
2734 "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"
2735 "\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"
2736 "\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"
2737 "\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"
2738 "\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"
2739 "\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"
2740 "\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"
2741 "\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"
2742 "\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"
2743 "\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"
2744 "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"
2745 "\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"
2746 "\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"
2747 "\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"
2748 "\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"
2749 "\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"
2750 "\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"
2751 "\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"
2752 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2753 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2754 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2755 "\0"]
2757 known_profile_icon = [ ""
2758 "GdkP"
2759 "\0\0\5""0"
2760 "\2\1\0\2"
2761 "\0\0\0P"
2762 "\0\0\0\24"
2763 "\0\0\0\24"
2764 "\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"
2765 "\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"
2766 "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"
2767 "\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"
2768 "\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"
2769 "\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"
2770 "\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"
2771 "\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"
2772 "\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"
2773 "\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"
2774 "\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"
2775 "\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"
2776 "\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"
2777 "\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"
2778 "\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"
2779 "\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"
2780 "\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"
2781 "\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"
2782 "\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"
2783 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2784 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2785 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2786 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2787 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2788 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2789 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2790 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2791 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2792 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2793 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2794 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2795 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2796 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2797 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2798 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2799 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2800 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2801 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2802 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2803 "\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"
2804 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2805 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2806 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2807 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2808 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2809 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2810 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2811 "\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"
2812 "\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"
2813 "\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"
2814 "\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"
2815 "\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"]
2817 unknown_profile_icon = [ ""
2818 "GdkP"
2819 "\0\0\5\22"
2820 "\2\1\0\2"
2821 "\0\0\0P"
2822 "\0\0\0\24"
2823 "\0\0\0\24"
2824 "\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"
2825 "\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"
2826 "\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"
2827 "\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"
2828 "(\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"
2829 "\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"
2830 "#\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"
2831 "\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"
2832 "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"
2833 "\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"
2834 "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"
2835 "\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"
2836 "\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"
2837 "\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"
2838 "\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"
2839 "\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"
2840 "\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"
2841 "\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"
2842 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2843 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2844 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2845 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2846 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2847 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2848 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2849 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2850 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2851 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2852 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2853 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2854 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2855 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2856 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2857 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2858 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2859 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2860 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2861 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2862 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2863 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2864 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2865 "\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"
2866 "\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"
2867 "\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"
2868 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2870 signal_xpm_barely = [
2871 "20 20 10 1",
2872 " c None",
2873 ". c #C6C6C6",
2874 "+ c #CCCCCC",
2875 "@ c #DBDBDB",
2876 "# c #D3D3D3",
2877 "$ c #A9B099",
2878 "% c #95A173",
2879 "& c #6B8428",
2880 "* c #B4B7AC",
2881 "= c #80924D",
2882 " .+++.",
2883 " +@@@+",
2884 " +@@@+",
2885 " +@@@+",
2886 " +@@@+",
2887 " .++++#@@@+",
2888 " +@@@@@@@@+",
2889 " +@@@@@@@@+",
2890 " +@@@@@@@@+",
2891 " +@@@@@@@@+",
2892 " $%%%%#@@@@@@@@+",
2893 " %&&&&@@@@@@@@@+",
2894 " %&&&&@@@@@@@@@+",
2895 " %&&&&@@@@@@@@@+",
2896 " %&&&&@@@@@@@@@+",
2897 "*%%%%=&&&&@@@@@@@@@+",
2898 "%&&&&&&&&&@@@@@@@@@+",
2899 "%&&&&&&&&&@@@@@@@@@+",
2900 "%&&&&&&&&&@@@@@@@@@+",
2901 "*%%%%%%%%%+++++++++."
2905 signal_xpm_best = [
2906 "20 20 6 1",
2907 " c None",
2908 ". c #9DAABF",
2909 "+ c #7B96BF",
2910 "@ c #386EBF",
2911 "# c #5982BF",
2912 "$ c #AEB4BF",
2913 " .+++.",
2914 " +@@@+",
2915 " +@@@+",
2916 " +@@@+",
2917 " +@@@+",
2918 " .++++#@@@+",
2919 " +@@@@@@@@+",
2920 " +@@@@@@@@+",
2921 " +@@@@@@@@+",
2922 " +@@@@@@@@+",
2923 " .++++#@@@@@@@@+",
2924 " +@@@@@@@@@@@@@+",
2925 " +@@@@@@@@@@@@@+",
2926 " +@@@@@@@@@@@@@+",
2927 " +@@@@@@@@@@@@@+",
2928 "$++++#@@@@@@@@@@@@@+",
2929 "+@@@@@@@@@@@@@@@@@@+",
2930 "+@@@@@@@@@@@@@@@@@@+",
2931 "+@@@@@@@@@@@@@@@@@@+",
2932 "$++++++++++++++++++."
2935 signal_xpm_none = [
2936 "20 20 6 1",
2937 " c None",
2938 ". c #C6C6C6",
2939 "+ c #CCCCCC",
2940 "@ c #DBDBDB",
2941 "# c #D3D3D3",
2942 "$ c #C2C2C2",
2943 " .+++.",
2944 " +@@@+",
2945 " +@@@+",
2946 " +@@@+",
2947 " +@@@+",
2948 " .++++#@@@+",
2949 " +@@@@@@@@+",
2950 " +@@@@@@@@+",
2951 " +@@@@@@@@+",
2952 " +@@@@@@@@+",
2953 " .++++#@@@@@@@@+",
2954 " +@@@@@@@@@@@@@+",
2955 " +@@@@@@@@@@@@@+",
2956 " +@@@@@@@@@@@@@+",
2957 " +@@@@@@@@@@@@@+",
2958 "$++++#@@@@@@@@@@@@@+",
2959 "+@@@@@@@@@@@@@@@@@@+",
2960 "+@@@@@@@@@@@@@@@@@@+",
2961 "+@@@@@@@@@@@@@@@@@@+",
2962 "$++++++++++++++++++."
2965 signal_xpm_ok = [
2966 "20 20 10 1",
2967 " c None",
2968 ". c #C6C6C6",
2969 "+ c #CCCCCC",
2970 "@ c #DBDBDB",
2971 "# c #A1A5B2",
2972 "$ c #848DA5",
2973 "% c #D3D3D3",
2974 "& c #4A5B8C",
2975 "* c #677498",
2976 "= c #B0B2B8",
2977 " .+++.",
2978 " +@@@+",
2979 " +@@@+",
2980 " +@@@+",
2981 " +@@@+",
2982 " #$$$$%@@@+",
2983 " $&&&&@@@@+",
2984 " $&&&&@@@@+",
2985 " $&&&&@@@@+",
2986 " $&&&&@@@@+",
2987 " #$$$$*&&&&@@@@+",
2988 " $&&&&&&&&&@@@@+",
2989 " $&&&&&&&&&@@@@+",
2990 " $&&&&&&&&&@@@@+",
2991 " $&&&&&&&&&@@@@+",
2992 "=$$$$*&&&&&&&&&@@@@+",
2993 "$&&&&&&&&&&&&&&@@@@+",
2994 "$&&&&&&&&&&&&&&@@@@+",
2995 "$&&&&&&&&&&&&&&@@@@+",
2996 "=$$$$$$$$$$$$$$++++."
3000 signal_xpm_low = [
3001 "20 20 8 1",
3002 " c None",
3003 ". c #C6C6C6",
3004 "+ c #CCCCCC",
3005 "@ c #DBDBDB",
3006 "# c #D3D3D3",
3007 "$ c #BFB0B5",
3008 "% c #C18799",
3009 "& c #C54F74",
3010 " .+++.",
3011 " +@@@+",
3012 " +@@@+",
3013 " +@@@+",
3014 " +@@@+",
3015 " .++++#@@@+",
3016 " +@@@@@@@@+",
3017 " +@@@@@@@@+",
3018 " +@@@@@@@@+",
3019 " +@@@@@@@@+",
3020 " .++++#@@@@@@@@+",
3021 " +@@@@@@@@@@@@@+",
3022 " +@@@@@@@@@@@@@+",
3023 " +@@@@@@@@@@@@@+",
3024 " +@@@@@@@@@@@@@+",
3025 "$%%%%#@@@@@@@@@@@@@+",
3026 "%&&&&@@@@@@@@@@@@@@+",
3027 "%&&&&@@@@@@@@@@@@@@+",
3028 "%&&&&@@@@@@@@@@@@@@+",
3029 "$%%%%++++++++++++++."
3033 ####################################################################################################
3034 # Make so we can be imported
3035 if __name__ == "__main__":
3036 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
3037 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
3038 else:
3039 import gtk, gobject
3040 gtk.gdk.threads_init()
3041 apQueue = Queue.Queue(100)
3042 commQueue = Queue.Queue(2)
3044 logger = logging.getLogger("wrlog")
3045 logger.setLevel(logging.WARNING)
3046 fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
3047 fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
3048 logger.addHandler(fileLogHandler)
3049 if __debug__:
3050 logger.setLevel(logging.DEBUG)
3051 consoleLogHandler = logging.StreamHandler()
3052 consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
3053 logger.addHandler(consoleLogHandler)
3055 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue, logger ) ).start()
3056 main_radar_window = radar_window(confFile, apQueue, commQueue, logger)
3057 gobject.timeout_add( 500, main_radar_window.update_plist_items )
3058 main_radar_window.main()