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