Update developer docs
[wifi-radar.git] / wifi-radar
bloba055e8f561647a48640d6b8ef7c8fe9220519de1
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.
26 import ConfigParser
27 import gtk
28 import os
29 import Queue
30 import re
31 import string
32 import sys
33 import threading
34 from signal import SIGTERM
35 from subprocess import call, Popen, PIPE
36 from time import sleep
37 from types import *
39 WIFI_RADAR_VERSION = "0.0.0"
41 if __debug__:
42 print '__debug__ is True'
43 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
44 # turn on debugging.
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.conf"
52 os.environ['LC_MESSAGES'] = 'C'
55 #####################################
56 # Labels
57 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
58 USE_IP_LABEL = "Manual network configuration"
59 WIFI_SET_LABEL = "WiFi Options"
60 CON_PP_LABEL = "Connection Commands"
61 DIS_PP_LABEL = "Disconnection Commands"
62 USE_WPA_LABEL = "Use WPA"
63 NO_WPA_LABEL = "No WPA"
64 ####################################################################################################
66 ####################################################################################################
67 ####################################################################################################
69 # Sets the interface to the specified network device
71 #Parameters:
73 # 'device' -- string - The network device to use
75 #Returns:
77 # nothing
78 def set_network_device( device ):
79 #print "set_network_device: ", device
80 if device != "auto_detect":
81 confFile.set_opt('DEFAULT.interface', device)
82 else: # auto detect network device
83 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
84 # If no devices are found, default to eth1.
85 # call iwconfig command and read output
86 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
87 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
88 if len(wireless_devices) > 0:
89 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
90 #else:
91 #print "No wifi-device found. Exiting."
92 #sys.exit()
94 # Return a blank profile
96 #Parameters:
98 # none
100 #Returns:
102 # dictionary -- An AP profile with defaults set.
103 def get_new_profile():
104 return { 'known': False,
105 'available': False,
106 'encrypted': False,
107 'essid': '',
108 'bssid': '',
109 'protocol': 'g',
110 'signal': 0,
111 'channel': 'auto',
112 'con_prescript': '',
113 'con_postscript': '',
114 'dis_prescript': '',
115 'dis_postscript': '',
116 'key': '',
117 'mode': '',
118 'security': '',
119 'use_wpa': False,
120 'wpa_driver': '',
121 'use_dhcp': True,
122 'ip': '',
123 'netmask': '',
124 'gateway': '',
125 'domain': '',
126 'dns1': '',
127 'dns2': ''
130 # Combine essid and bssid to make a config file section name
132 #Parameters:
134 # 'essid' -- string - AP ESSID
136 # 'bssid' -- string - AP BSSID
138 #Returns:
140 # string -- the bssid concatenated to a colon, concatenated to the essid
141 def make_section_name( essid, bssid ):
142 return essid + ':' + bssid
144 # Split a config file section name into an essid and a bssid
146 #Parameters:
148 # 'section' -- string - Config file section name
150 #Returns:
152 # list -- the essid and bssid
153 def split_section_name( section ):
154 parts = re.split(':', section)
155 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
157 # Run commands through the shell
159 #Parameters:
161 # 'command' -- tuple - The command and arguments to run
163 #Returns:
165 # boolean -- True on success, otherwise, False
166 def shellcmd( command, environment = None ):
167 try:
168 env_tmp = os.environ
169 env_tmp.update(environment)
170 command = ' '.join(command)
171 return_code = call(command, shell=True, env=env_tmp)
172 if return_code >= 0:
173 return True
174 else:
175 print >>sys.stderr, "Child was terminated by signal", -return_code
176 except OSError, exception:
177 print >>sys.stderr, "Execution failed:", exception
178 return False
180 # Speak feedback message to user
182 #Parameters:
184 # 'words' -- string - Message to speak to user
186 #Returns:
188 # nothing
189 def say( words ):
190 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
191 words = words.replace( "\"", "\\\"" )
192 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
194 # Scan for a limited time and return AP names and bssid found.
195 # Access points we find will be put on the outgoing Queue, apQueue.
197 #Parameters:
199 # 'confFile' -- ConfigFile - Config file object
201 # 'apQueue' -- Queue - Queue on which to put AP profiles
203 # 'commandQueue' -- Queue - Queue from which to read commands
205 #Returns:
207 # nothing
208 def scanning_thread( confFile, apQueue, commandQueue ):
209 # Setup our essid pattern matcher
210 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
211 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abg]+)", re.I | re.M | re.S )
212 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
213 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
214 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
215 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
217 access_points = {}
218 command = "scan"
219 while True:
220 try:
221 command = commandQueue.get_nowait()
222 if __debug__: print "scanning_thread received command:", command
223 command_read = True
224 except Queue.Empty:
225 command_read = False
226 if command == "scan":
227 #if __debug__: print "Beginning scan pass"
228 # Some cards need to have the interface up to scan
229 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
230 # call ifconfig command and wait for return
231 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
232 # update the signal strengths
233 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
234 #if __debug__:
235 #print "Current IP ", get_current_ip()
236 #print "Current ESSID ", get_current_essid()
237 #print "Current BSSID ", get_current_bssid()
238 # zero out the signal levels for all access points
239 for bssid in access_points:
240 access_points[bssid]['signal'] = 0
241 # split the scan data based on the address line
242 hits = scandata.split(' - ')
243 for hit in hits:
244 # set the defaults for profile template
245 profile = get_new_profile()
246 m = essid_pattern.search( hit )
247 if m:
248 # we found an essid
249 profile['essid'] = m.groups()[1]
250 m = bssid_pattern.search( hit ) # get BSSID from scan
251 if m: profile['bssid'] = m.groups()[1]
252 m = protocol_pattern.search( hit ) # get protocol from scan
253 if m: profile['protocol'] = m.groups()[1]
254 m = mode_pattern.search( hit ) # get mode from scan
255 if m: profile['mode'] = m.groups()[1]
256 m = channel_pattern.search( hit ) # get channel from scan
257 if m: profile['channel'] = m.groups()[1]
258 m = enckey_pattern.search( hit ) # get encryption key from scan
259 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
260 m = signal_pattern.search( hit ) # get signal strength from scan
261 if m: profile['signal'] = m.groups()[1]
262 access_points[ profile['bssid'] ] = profile
263 for bssid in access_points:
264 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
265 # Put all, now or previously, sensed access_points into apQueue
266 try:
267 apQueue.put_nowait( access_points[bssid] )
268 except Queue.Full:
269 pass
270 elif command == "exit":
271 if __debug__: print "Exiting scanning_thread"
272 return
273 if command_read: commandQueue.task_done()
274 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
275 sleep( 3 )
276 else:
277 sleep( 1 )
280 # Manage a connection; including reporting connection state,
281 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
282 class ConnectionManager():
283 # Create a new connection manager which can read a config file and send to scanning thread
284 # command Queue. A new manager checks for a pre-existing connection and takes
285 # its AP profile from the ESSID and BSSID to which it is currently attached.
287 #Parameters:
289 # 'confFile' -- ConfigFile - Config file object
291 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
293 #Returns:
295 # ConnectionManager instance
296 def __init__( self, confFile, commandQueue ):
297 self.confFile = confFile
298 self.commQueue = commandQueue
299 # is connection running?
300 self.state = False
301 if self.get_current_ip():
302 self.state = True
303 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
305 # Change the interface state: up or down.
307 #Parameters:
309 # 'state' -- string - The state to which to change the interface.
311 #Returns:
313 # nothing
314 def if_change( self, state ):
315 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
316 if __debug__: print "if_change: changing interface state to %s" % state
317 # call ifconfig command and wait for return
318 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
320 # Connect to the specified AP.
322 #Parameters:
324 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
326 # 'status' -- status implementer - Object which implements status interface.
328 #Returns:
330 # nothing
331 def connect_to_network( self, profile, status ):
332 self.profile = profile
333 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
334 say( msg )
335 if __debug__: print " %s" % msg
336 # ready to dance
337 # Let's run the connection prescript
338 if self.profile['con_prescript'].strip() != '':
339 # got something to execute
340 # run connection prescript through shell and wait for return
341 if __debug__: print "executing connection prescript:", self.profile['con_prescript']
342 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
343 status.show()
344 # Some cards need to have the interface up
345 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
346 self.if_change('up')
347 # Start building iwconfig command line, command
348 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
349 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
350 # Setting essid
351 iwconfig_command.append( 'essid' )
352 iwconfig_command.append( self.profile['essid'] )
353 # Setting nick
354 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
355 # Setting key
356 iwconfig_command.append( 'key' )
357 if self.profile['key'] == '':
358 iwconfig_command.append( 'off' )
359 else:
360 iwconfig_command.append( self.profile['key'] )
361 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
362 # Setting mode to Master, this will cause association with the AP to fail
363 #iwconfig_command.append( 'mode' )
364 #iwconfig_command.append( 'Master' )
365 # Setting channel
366 if self.profile['channel'] != '':
367 iwconfig_command.append( 'channel' )
368 iwconfig_command.append( self.profile['channel'] )
369 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
370 iwconfig_command.append( 'ap' )
371 iwconfig_command.append( self.profile['bssid'] )
372 # Some cards require a commit
373 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
374 if __debug__: print 'iwconfig_args %s ' % ( iwconfig_args, )
375 iwconfig_command.append( 'commit' )
376 # call iwconfig command and wait for return
377 if not shellcmd(iwconfig_command): return
378 # Now normal network stuff
379 # Kill off any existing DHCP clients running
380 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
381 if __debug__: print "Killing existing DHCP..."
382 try:
383 if self.confFile.get_opt('DHCP.kill_args') != '':
384 # call DHCP client kill command and wait for return
385 if __debug__: print self.confFile.get_opt('DHCP.command') + " " + self.confFile.get_opt('DHCP.kill_args')
386 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
387 else:
388 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
389 except OSError:
390 print "failed to kill DHCP client"
391 sys.exit()
392 finally:
393 print "Stale pid file. Removing..."
394 os.remove(self.confFile.get_opt('DHCP.pidfile'))
395 # Kill off any existing WPA supplicants running
396 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
397 if __debug__: print "Killing existing WPA supplicant..."
398 try:
399 if not self.confFile.get_opt('WPA.kill_command') != '':
400 # call WPA supplicant kill command and wait for return
401 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
402 else:
403 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
404 except OSError:
405 print "failed to kill WPA supplicant"
406 sys.exit()
407 finally:
408 print "Stale pid file. Removing..."
409 os.remove(self.confFile.get_opt('WPA.pidfile'))
410 # Begin WPA supplicant
411 if self.profile['use_wpa'] :
412 if __debug__: print "WPA args: %s" % ( wpa_options, )
413 status.update_message("WPA supplicant starting")
414 if sys.modules.has_key("gtk"):
415 while gtk.events_pending():
416 gtk.main_iteration(False)
417 # call WPA supplicant command and do not wait for return
418 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
419 if self.profile['use_dhcp'] :
420 if __debug__: print "Disable iwlist while dhcp in progress..."
421 try:
422 self.commQueue.put("pause")
423 except Queue.Full:
424 pass
425 status.update_message("Acquiring IP Address (DHCP)")
426 if sys.modules.has_key("gtk"):
427 while gtk.events_pending():
428 gtk.main_iteration(False)
429 # call DHCP client command and do not wait for return
430 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
431 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
432 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
433 dhcp_proc = Popen(dhcp_command, stdout=None)
434 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
435 tick = 0.25
436 waiting = dhcp_proc.poll()
437 while waiting == None:
438 waiting = dhcp_proc.poll()
439 if timer < 0:
440 os.kill(dhcp_proc.pid, SIGTERM)
441 break
442 if sys.modules.has_key("gtk"):
443 while gtk.events_pending():
444 gtk.main_iteration(False)
445 timer -= tick
446 sleep(tick)
447 if not self.get_current_ip():
448 status.update_message("Could not get IP address!")
449 if sys.modules.has_key("gtk"):
450 while gtk.events_pending():
451 gtk.main_iteration(False)
452 sleep(1)
453 if self.state:
454 self.disconnect_interface()
455 else:
456 status.update_message("Got IP address. Done.")
457 self.state = True
458 if sys.modules.has_key("gtk"):
459 while gtk.events_pending():
460 gtk.main_iteration(False)
461 sleep(2)
462 # Re-enable iwlist
463 try:
464 self.commQueue.put("scan")
465 except Queue.Full:
466 pass
467 else:
468 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'] )
469 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
470 resolv_contents = ''
471 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
472 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
473 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
474 if ( resolv_contents != '' ):
475 resolv_file=open('/etc/resolv.conf', 'w')
476 resolv_file.write(s)
477 resolv_file.close
478 if not shellcmd([ifconfig_command]): return
479 if not shellcmd([route_command]): return
480 self.state = True
481 # Let's run the connection postscript
482 con_postscript = self.profile['con_postscript']
483 if self.profile['con_postscript'].strip() != '':
484 if __debug__: print "executing connection postscript:", self.profile['con_postscript']
485 shellcmd([self.profile['con_postscript']], environment={"WIFIRADAR_IP": self.get_current_ip(), "WIFIRADAR_ESSID": self.get_current_essid(), "WIFIRADAR_BSSID": self.get_current_bssid(), "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
486 status.hide()
488 # Disconnect from the AP with which a connection has been established/attempted.
490 #Parameters:
492 # nothing
494 #Returns:
496 # nothing
497 def disconnect_interface( self ):
498 msg = "Disconnecting"
499 say( msg )
500 if __debug__: print msg
501 # Pause scanning while manipulating card
502 try:
503 self.commQueue.put("pause")
504 except Queue.Full:
505 pass
506 if self.state:
507 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
508 # Let's run the disconnection prescript
509 if self.profile['dis_prescript'].strip() != '':
510 if __debug__: print "executing disconnection prescript:", self.profile['dis_prescript']
511 shellcmd([self.profile['dis_prescript']], environment={"WIFIRADAR_IP": self.get_current_ip(), "WIFIRADAR_ESSID": self.get_current_essid(), "WIFIRADAR_BSSID": self.get_current_bssid(), "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
512 if __debug__: print "Kill off any existing DHCP clients running..."
513 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
514 if __debug__: print "Killing existing DHCP..."
515 try:
516 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
517 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
518 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
519 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
520 if __debug__: print "DHCP command", dhcp_command
521 # call DHCP client command and wait for return
522 if not shellcmd(dhcp_command): return
523 else:
524 if __debug__: print "killing DHCP manually."
525 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
526 except OSError:
527 print "failed to kill DHCP client"
528 if __debug__: print "Kill off any existing WPA supplicants running..."
529 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
530 if __debug__: print "Killing existing WPA supplicant..."
531 try:
532 if not self.confFile.get_opt('WPA.kill_command') != '':
533 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
534 if not shellcmd(wpa_command): return
535 else:
536 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
537 except OSError:
538 print "failed to kill WPA supplicant"
539 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
540 if __debug__: print "Let's clear out the wireless stuff"
541 if __debug__: print 'Now take the interface down'
542 # taking down the interface too quickly can crash my system, so pause a moment
543 sleep(1)
544 self.if_change('down')
545 if __debug__: print 'Since it may be brought back up by the next scan, lets unset its IP'
546 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
547 # Let's run the disconnection postscript
548 if self.profile['dis_postscript'].strip() != '':
549 if __debug__: print "executing disconnection postscript:", self.profile['dis_postscript']
550 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
551 self.state = False
552 if __debug__: print 'Disconnect complete.'
553 # Begin scanning again
554 try:
555 self.commQueue.put("scan")
556 except Queue.Full:
557 pass
559 # Returns the current IP, if any, by calling ifconfig.
561 #Parameters:
563 # nothing
565 #Returns:
567 # string or None -- the IP address or None (if no there is no current connection)
568 def get_current_ip( self ):
569 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
570 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
571 # Be careful to the language (inet adr: in French for example)
573 # Hi Brian
575 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
576 # There the string in ifconfig is inet Adresse for the IP which isn't
577 # found by the current get_current_ip function in wifi-radar. I changed
578 # the according line (#289; gentoo, v1.9.6-r1) to
579 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
580 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
582 # I'd be happy if you could incorporate this small change because as now
583 # I've got to change the file every time it is updated.
585 # Best wishes
587 # Simon
588 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
589 line = ifconfig_info.read()
590 if ip_re.search( line ):
591 return ip_re.search( line ).group(1)
592 return None
594 # Returns the current ESSID, if any, by calling iwconfig.
596 #Parameters:
598 # nothing
600 #Returns:
602 # string or None -- the ESSID or None (if no there is no current association)
603 def get_current_essid( self ):
604 """Returns the current ESSID if any by calling iwconfig"""
605 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
606 # Be careful to the language (inet adr: in French for example)
607 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
608 line = iwconfig_info.read()
609 if essid_re.search( line ):
610 return essid_re.search( line ).group(2)
611 return None
613 # Returns the current BSSID, if any, by calling iwconfig.
615 #Parameters:
617 # nothing
619 #Returns:
621 # string or None -- the BSSID or None (if no there is no current association)
622 def get_current_bssid( self ):
623 """Returns the current BSSID if any by calling iwconfig"""
624 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
625 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
626 line = iwconfig_info.read()
627 if bssid_re.search( line ):
628 return bssid_re.search( line ).group(2)
629 return None
633 # The main user interface window for WiFi Radar. This class also is the control
634 # center for most of the rest of the operations.
635 class radar_window:
636 # Create a new radar_window.
638 #Parameters:
640 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
642 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
644 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
646 #Returns:
648 # radar_window instance
649 def __init__( self, confFile, apQueue, commQueue ):
650 global signal_xpm_none
651 global signal_xpm_low
652 global signal_xpm_barely
653 global signal_xpm_ok
654 global signal_xpm_best
655 global known_profile_icon
656 global unknown_profile_icon
657 global wifi_radar_icon
659 self.confFile = confFile
660 self.apQueue = apQueue
661 self.commandQueue = commQueue
662 self.access_points = {}
663 self.connection = None
665 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
666 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
667 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
668 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
669 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
670 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
671 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
672 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
673 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
674 self.window.set_icon( icon )
675 self.window.set_border_width( 10 )
676 self.window.set_size_request( 550, 300 )
677 self.window.set_title( "WiFi Radar" )
678 self.window.connect( 'delete_event', self.delete_event )
679 self.window.connect( 'destroy', self.destroy )
680 # let's create all our widgets
681 self.current_network = gtk.Label()
682 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
683 self.current_network.show()
684 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
685 self.close_button.show()
686 self.close_button.connect( 'clicked', self.delete_event, None )
687 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
688 self.about_button.show()
689 self.about_button.connect( 'clicked', self.show_about_info, None )
690 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
691 self.preferences_button.show()
692 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
693 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
694 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
695 self.plist = gtk.TreeView( self.pstore )
696 # The icons column, known and encryption
697 self.pix_cell = gtk.CellRendererPixbuf()
698 self.wep_cell = gtk.CellRendererPixbuf()
699 self.icons_cell = gtk.CellRendererText()
700 self.icons_col = gtk.TreeViewColumn()
701 self.icons_col.pack_start( self.pix_cell, False )
702 self.icons_col.pack_start( self.wep_cell, False )
703 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
704 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
705 self.plist.append_column( self.icons_col )
706 # The AP column
707 self.ap_cell = gtk.CellRendererText()
708 self.ap_col = gtk.TreeViewColumn( "Access Point" )
709 self.ap_col.pack_start( self.ap_cell, True )
710 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
711 self.plist.append_column( self.ap_col )
712 # The signal column
713 self.sig_cell = gtk.CellRendererPixbuf()
714 self.signal_col = gtk.TreeViewColumn( "Signal" )
715 self.signal_col.pack_start( self.sig_cell, True )
716 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
717 self.plist.append_column( self.signal_col )
718 # The mode column
719 self.mode_cell = gtk.CellRendererText()
720 self.mode_col = gtk.TreeViewColumn( "Mode" )
721 self.mode_col.pack_start( self.mode_cell, True )
722 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
723 self.plist.append_column( self.mode_col )
724 # The protocol column
725 self.prot_cell = gtk.CellRendererText()
726 self.protocol_col = gtk.TreeViewColumn( "802.11" )
727 self.protocol_col.pack_start( self.prot_cell, True )
728 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
729 self.plist.append_column( self.protocol_col )
730 # The channel column
731 self.channel_cell = gtk.CellRendererText()
732 self.channel_col = gtk.TreeViewColumn( "Channel" )
733 self.channel_col.pack_start( self.channel_cell, True )
734 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
735 self.plist.append_column( self.channel_col )
736 # DnD Ordering
737 self.plist.set_reorderable( True )
738 self.pstore.connect( 'row-changed', self.update_auto_profile_order )
739 # enable/disable buttons based on the selected network
740 self.selected_network = self.plist.get_selection()
741 self.selected_network.connect( 'changed', self.on_network_selection, None )
742 # the list scroll bar
743 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
744 sb.show()
745 self.plist.show()
746 # Add New button
747 self.new_button = gtk.Button( "_New" )
748 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
749 self.new_button.show()
750 # Add Configure button
751 self.edit_button = gtk.Button( "C_onfigure" )
752 self.edit_button.connect( 'clicked', self.edit_profile, None )
753 self.edit_button.show()
754 self.edit_button.set_sensitive(False)
755 # Add Delete button
756 self.delete_button = gtk.Button( "_Delete" )
757 self.delete_button.connect( 'clicked', self.delete_profile, None )
758 self.delete_button.show()
759 self.delete_button.set_sensitive(False)
760 # Add Connect button
761 self.connect_button = gtk.Button( "Co_nnect" )
762 self.connect_button.connect( 'clicked', self.connect_profile, None )
763 # Add Disconnect button
764 self.disconnect_button = gtk.Button( "D_isconnect" )
765 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
766 # lets add our widgets
767 rows = gtk.VBox( False, 3 )
768 net_list = gtk.HBox( False, 0 )
769 listcols = gtk.HBox( False, 0 )
770 prows = gtk.VBox( False, 0 )
771 # lets start packing
772 # the network list
773 net_list.pack_start( self.plist, True, True, 0 )
774 net_list.pack_start( sb, False, False, 0 )
775 # the rows level
776 rows.pack_start( net_list , True, True, 0 )
777 rows.pack_start( self.current_network, False, True, 0 )
778 # the list columns
779 listcols.pack_start( rows, True, True, 0 )
780 listcols.pack_start( prows, False, False, 5 )
781 # the list buttons
782 prows.pack_start( self.new_button, False, False, 2 )
783 prows.pack_start( self.edit_button, False, False, 2 )
784 prows.pack_start( self.delete_button, False, False, 2 )
785 prows.pack_end( self.connect_button, False, False, 2 )
786 prows.pack_end( self.disconnect_button, False, False, 2 )
788 self.window.action_area.pack_start( self.about_button )
789 self.window.action_area.pack_start( self.preferences_button )
790 self.window.action_area.pack_start( self.close_button )
792 rows.show()
793 prows.show()
794 listcols.show()
795 self.window.vbox.add( listcols )
796 self.window.vbox.set_spacing( 3 )
797 self.window.show_all()
799 # Now, immediately hide these two. The proper one will be
800 # displayed later, based on interface state. -BEF-
801 self.disconnect_button.hide()
802 self.connect_button.hide()
803 self.connect_button.set_sensitive(False)
805 # set up connection manager for later use
806 self.connection = ConnectionManager( self.confFile, self.commandQueue )
807 # set up status window for later use
808 self.status_window = StatusWindow( self )
809 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
811 # Add our known profiles in order
812 for ap in self.confFile.auto_profile_order:
813 ap = ap.strip()
814 self.access_points[ ap ] = self.confFile.get_profile( ap )
815 wep = None
816 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
817 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'] ] )
818 # This is the first run (or, at least, no config file was present), so pop up the preferences window
819 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
820 self.confFile.remove_option('DEFAULT', 'new_file')
821 self.edit_preferences(self.preferences_button)
823 # Begin running radar_window in Gtk event loop.
825 #Parameters:
827 # nothing
829 #Returns:
831 # nothing
832 def main( self ):
833 gtk.main()
835 # Write the config file to disk and quit application.
837 #Parameters:
839 # 'widget' -- gtk.Widget - The widget sending the event.
841 #Returns:
843 # nothing
844 def destroy( self, widget = None):
845 self.confFile.write()
846 if self.status_window:
847 self.status_window.destroy()
848 gtk.main_quit()
850 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
852 #Parameters:
854 # 'widget' -- gtk.Widget - The widget sending the event.
856 # 'data' -- tuple - list of arbitrary arguments (not used)
858 #Returns:
860 # boolean -- always return False (i.e. do not propigate the signal which called)
861 def delete_event( self, widget, data = None ):
862 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
863 try:
864 self.commandQueue.put("exit", True)
865 except Queue.Full:
866 pass
867 # Save the preferred networks order
868 self.update_auto_profile_order()
869 self.destroy()
870 return False
872 # Updates the on-screen profiles list.
874 #Parameters:
876 # nothing
878 #Returns:
880 # boolean -- always return True
881 def update_plist_items( self ):
882 # Indicate to PyGtk that only one Gtk thread should run here
883 gtk.gdk.threads_enter()
884 # update the current ip and essid
885 # set the state of connect/disconnect buttons based on whether we have an IP address
886 if self.connection:
887 if self.connection.state:
888 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() ) )
889 self.connect_button.hide()
890 self.disconnect_button.show()
891 else:
892 self.current_network.set_text( "Not Connected." )
893 self.disconnect_button.hide()
894 self.connect_button.show()
896 while True:
897 # Get profiles scanned by iwlist
898 try:
899 profile = self.apQueue.get_nowait()
900 except Queue.Empty:
901 break
902 else:
903 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
904 wep = None
905 if prow_iter != None:
906 # the AP is in the list of APs on the screen
907 apname = make_section_name(profile['essid'], profile['bssid'])
908 if self.access_points.has_key(apname):
909 # This AP has been configured and is/should be stored in the config file
910 profile['known'] = self.access_points[apname]['known']
911 self.access_points[apname]['available'] = profile['available']
912 self.access_points[apname]['encrypted'] = profile['encrypted']
913 self.access_points[apname]['signal'] = profile['signal']
914 self.access_points[apname]['mode'] = profile['mode']
915 self.access_points[apname]['protocol'] = profile['protocol']
916 self.access_points[apname]['channel'] = profile['channel']
917 # Set the 'known' values; False is default, overridden to True by self.access_points
918 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
919 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
920 self.pstore.set_value(prow_iter, 3, profile['available'])
921 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
922 self.pstore.set_value(prow_iter, 4, wep)
923 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
924 self.pstore.set_value(prow_iter, 6, profile['mode'])
925 self.pstore.set_value(prow_iter, 7, profile['protocol'])
926 self.pstore.set_value(prow_iter, 8, profile['channel'])
927 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
928 #for val in self.pstore[prow_iter]:
929 #print val,
930 else:
931 # the AP is not in the list of APs on the screen
932 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'] ] )
933 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
934 # Allow other Gtk threads to run
935 gtk.gdk.threads_leave()
936 #print "update_plist_items: Empty apQueue"
937 return True
939 # Return the proper icon for a value of known.
941 #Parameters:
943 # 'known' -- boolean - Whether the AP is known (i.e. configured)
945 #Returns:
947 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
948 def pixbuf_from_known( self, known ):
949 """ return the proper icon for value of known """
950 if known:
951 return self.known_profile_icon
952 else:
953 return self.unknown_profile_icon
955 # Return an icon indicating the signal level.
957 #Parameters:
959 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
961 #Returns:
963 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
964 def pixbuf_from_signal( self, signal ):
965 signal = int( signal )
966 #print "signal level:", signal
967 if signal < 3:
968 return self.signal_none_pb
969 elif signal < 12:
970 return self.signal_low_pb
971 elif signal < 20:
972 return self.signal_barely_pb
973 elif signal < 35:
974 return self.signal_ok_pb
975 elif signal >= 35:
976 return self.signal_best_pb
977 else:
978 return None
980 # Return row which holds specified ESSID and BSSID.
982 #Parameters:
984 # 'essid' -- string - ESSID to match
986 # 'bssid' -- string - BSSID to match
988 #Returns:
990 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
991 def get_row_by_ap( self, essid, bssid ):
992 for row in self.pstore:
993 if ( row[0] == essid + "\n" + bssid ):
994 #print "matched:", row.iter, essid, bssid
995 return row.iter
996 return None
998 # Enable/disable buttons based on the selected network.
1000 #Parameters:
1002 # 'widget' -- gtk.Widget - The widget sending the event.
1004 # 'data' -- tuple - list of arbitrary arguments (not used)
1006 #Returns:
1008 # nothing
1009 def on_network_selection( self, widget, data = None ):
1010 ( store, selected_iter ) = self.selected_network.get_selected()
1011 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1012 # if no networks are selected, disable all buttons except New
1013 # (this occurs after a drag-and-drop)
1014 if selected_iter == None:
1015 self.edit_button.set_sensitive(False)
1016 self.delete_button.set_sensitive(False)
1017 self.connect_button.set_sensitive(False)
1018 return
1019 # enable/disable buttons
1020 self.connect_button.set_sensitive(True)
1021 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1022 self.edit_button.set_sensitive(True)
1023 self.delete_button.set_sensitive(True)
1024 else:
1025 self.edit_button.set_sensitive(True)
1026 self.delete_button.set_sensitive(False)
1028 # Init and run the about dialog
1030 #Parameters:
1032 # 'widget' -- gtk.Widget - The widget sending the event.
1034 # 'data' -- tuple - list of arbitrary arguments (not used)
1036 #Returns:
1038 # nothing
1039 def show_about_info( self, widget, data=None ):
1040 about = about_dialog()
1041 about.run()
1042 about.destroy()
1044 # Init and run the preferences dialog
1046 #Parameters:
1048 # 'widget' -- gtk.Widget - The widget sending the event.
1050 # 'data' -- tuple - list of arbitrary arguments (not used)
1052 #Returns:
1054 # nothing
1055 def edit_preferences( self, widget, data=None ):
1056 prefs = preferences_dialog( self, self.confFile )
1057 response = prefs.run()
1058 prefs.destroy()
1059 if response == int(gtk.RESPONSE_ACCEPT):
1060 prefs.save()
1062 # Respond to a request to create a new AP profile
1064 #Parameters:
1066 # 'widget' -- gtk.Widget - The widget sending the event.
1068 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1070 # 'data' -- tuple - list of arbitrary arguments (not used)
1072 #Returns:
1074 # boolean -- True if a profile was created and False if profile creation was canceled.
1075 def create_new_profile( self, widget, profile, data=None ):
1076 profile_editor = profile_dialog( self, profile )
1077 profile = profile_editor.run()
1078 profile_editor.destroy()
1079 if profile:
1080 apname = make_section_name( profile['essid'], profile['bssid'] )
1081 # Check that the ap does not exist already
1082 if apname in self.confFile.profiles():
1083 dlg = gtk.MessageDialog( self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "A profile for %s already exists" % (apname) )
1084 dlg.run()
1085 dlg.destroy()
1086 del dlg
1087 # try again
1088 self.access_points[ apname ] = profile
1089 self.confFile.set_section( apname, profile )
1090 # if it is not in the auto_profile_order add it
1091 if apname not in self.confFile.auto_profile_order:
1092 self.confFile.auto_profile_order.append(apname)
1093 # add to the store
1094 wep = None
1095 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1096 return True
1097 else:
1098 # Did not create new profile
1099 return False
1101 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1102 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1104 #Parameters:
1106 # 'widget' -- gtk.Widget - The widget sending the event.
1108 # 'data' -- tuple - list of arbitrary arguments (not used)
1110 #Returns:
1112 # nothing
1113 def edit_profile( self, widget, data=None ):
1114 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1115 if not selected_iter: return
1116 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1117 apname = make_section_name( row_start[0], row_start[1] )
1118 profile = self.confFile.get_profile( apname )
1119 if profile:
1120 profile_editor = profile_dialog( self, profile )
1121 profile = profile_editor.run()
1122 profile_editor.destroy()
1123 if profile:
1124 if __debug__:
1125 print "Got edited profile ", profile
1126 apname = make_section_name( profile['essid'], profile['bssid'] )
1127 self.access_points[ apname ] = profile
1128 self.confFile.set_section( apname, profile )
1129 else:
1130 profile = get_new_profile()
1131 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1132 self.create_new_profile( widget, profile, data )
1134 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1136 #Parameters:
1138 # 'widget' -- gtk.Widget - The widget sending the event.
1140 # 'data' -- tuple - list of arbitrary arguments (not used)
1142 #Returns:
1144 # nothing
1145 def delete_profile( self, widget, data=None ):
1146 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1147 if not selected_iter: return
1148 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1149 known = store.get_value( selected_iter, 1 )
1150 if not known: return
1151 dlg = gtk.MessageDialog(
1152 self.window,
1153 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1154 gtk.MESSAGE_QUESTION,
1155 gtk.BUTTONS_YES_NO,
1156 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1157 res = dlg.run()
1158 dlg.destroy()
1159 del dlg
1160 if res == gtk.RESPONSE_NO: return
1161 # Remove it
1162 apname = make_section_name( essid, bssid )
1163 del self.access_points[ apname ]
1164 self.confFile.remove_section( apname )
1165 print "delete_profile: ", apname, ":", self.confFile.auto_profile_order
1166 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1167 self.pstore.remove( selected_iter )
1168 # Let's save our current state
1169 self.update_auto_profile_order()
1171 # Respond to a request to connect to an AP.
1173 #Parameters:
1175 # 'widget' -- gtk.Widget - The widget sending the event.
1177 # 'profile' -- dictionary - The AP profile to which to connect.
1179 # 'data' -- tuple - list of arbitrary arguments (not used)
1181 #Returns:
1183 # nothing
1184 def connect_profile( self, widget, profile, data=None ):
1185 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1186 if not selected_iter: return
1187 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1188 known = store.get_value( selected_iter, 2 )
1189 if not known:
1190 if data != 'noconnect':
1191 dlg = gtk.MessageDialog(
1192 self.window,
1193 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1194 gtk.MESSAGE_QUESTION,
1195 gtk.BUTTONS_YES_NO,
1196 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1197 res = dlg.run()
1198 dlg.destroy()
1199 del dlg
1200 if res == gtk.RESPONSE_NO: return
1201 profile = get_new_profile()
1202 profile['essid'] = essid
1203 profile['bssid'] = bssid
1204 if not self.create_new_profile( widget, profile, data ):
1205 return
1206 apname = make_section_name( essid, bssid )
1207 self.connection.connect_to_network(self.access_points[apname], self.status_window)
1209 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1211 #Parameters:
1213 # 'widget' -- gtk.Widget - The widget sending the event.
1215 # 'data' -- tuple - list of arbitrary arguments (not used)
1217 #Returns:
1219 # nothing
1220 def disconnect_profile( self, widget, data=None ):
1221 if data == "cancel":
1222 self.status_window.update_message("Canceling connection...")
1223 if sys.modules.has_key("gtk"):
1224 while gtk.events_pending():
1225 gtk.main_iteration(False)
1226 sleep(1)
1227 self.connection.disconnect_interface()
1229 # Update the config file auto profile order from the on-screen order
1231 #Parameters:
1233 # 'widget' -- gtk.Widget - The widget sending the event.
1235 # 'data' -- tuple - list of arbitrary arguments (not used)
1237 # 'data2' -- tuple - list of arbitrary arguments (not used)
1239 #Returns:
1241 # nothing
1242 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1243 # recreate the auto_profile_order
1244 auto_profile_order = []
1245 piter = self.pstore.get_iter_first()
1246 while piter:
1247 # only if it's known
1248 if self.pstore.get_value( piter, 2 ) == True:
1249 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1250 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1251 piter = self.pstore.iter_next( piter )
1252 self.confFile.auto_profile_order = auto_profile_order
1255 # Button to allow user to choose a file and put value into specified gtk.Entry
1256 class file_browse_button(gtk.Button):
1257 # Create a button to simulate a File/Open
1259 #Parameters:
1261 # 'parent' -- gtk.Object -- Usually, the calling window.
1263 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1265 #Returns:
1267 # file_browse_button instance
1268 def __init__( self, parent, entry ):
1269 self.parent_window = parent
1270 self.entry = entry
1271 gtk.Button.__init__(self, "Browse", None)
1272 #self.
1273 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)
1274 self.connect("clicked", self.browse_files)
1276 # Show filechooser dialog and get user selection
1278 #Parameters:
1280 # 'widget' -- gtk.Widget -- The widget sending the event.
1282 #Returns:
1284 # nothing
1286 #NOTES:
1288 # updates entry value
1290 def browse_files( self, widget ):
1291 self.browser_dialog.set_filename(self.entry.get_text())
1292 self.browser_dialog.run()
1293 self.entry.set_text(self.browser_dialog.get_filename())
1294 self.browser_dialog.destroy()
1297 # The preferences dialog. Edits some items in the DEFAULT section of the config file.
1298 class preferences_dialog:
1299 # Create a new preferences_dialog.
1301 #Parameters:
1303 # 'parent' -- gtk.Object - Usually, the calling window.
1305 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1307 #Returns:
1309 # preferences_dialog instance
1310 def __init__( self, parent, confFile ):
1311 global wifi_radar_icon
1312 self.parent = parent
1313 self.confFile = confFile
1314 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1315 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1316 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1317 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1318 self.dialog.set_icon( icon )
1319 self.dialog.set_resizable( True )
1320 self.dialog.set_transient_for( self.parent.window )
1321 self.tooltips = gtk.Tooltips()
1323 # set up preferences widgets
1326 # auto detect wireless device
1327 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1329 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1331 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1332 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1333 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1334 self.dialog.vbox.pack_start(self.w_auto_detect, False, False, 5)
1336 # network interface selecter
1337 self.w_interface = gtk.combo_box_entry_new_text()
1338 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1339 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1340 for device in wireless_devices:
1341 if device != self.confFile.get_opt('DEFAULT.interface'):
1342 self.w_interface.append_text(device)
1343 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1344 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1345 self.w_interface.set_active(0)
1346 self.w_interface_label = gtk.Label("Wireless device")
1347 self.w_hbox1 = gtk.HBox(False, 0)
1348 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1349 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1350 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1351 self.dialog.vbox.pack_start(self.w_hbox1, False, False, 5)
1353 # scan timeout (spin button of integers from 1 to 100)
1354 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,1 )
1355 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1356 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1357 self.w_scan_timeout.set_numeric(True)
1358 self.w_scan_timeout.set_snap_to_ticks(True)
1359 self.w_scan_timeout.set_wrap(False)
1360 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1361 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1362 self.w_hbox2 = gtk.HBox(False, 0)
1363 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1364 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1365 self.dialog.vbox.pack_start(self.w_hbox2, False, False, 5)
1367 # speak up
1368 self.w_speak_up = gtk.CheckButton("Use speak-up")
1369 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1370 self.w_speak_up.connect("toggled", self.toggle_speak)
1371 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1372 self.dialog.vbox.pack_start(self.w_speak_up, False, False, 5)
1374 # speak up command
1375 self.w_speak_cmd = gtk.Entry()
1376 self.w_speak_cmd.set_width_chars(16)
1377 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1378 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1379 self.w_speak_cmd_label = gtk.Label("Speak Command")
1380 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1381 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1382 self.w_hbox3 = gtk.HBox(False, 0)
1383 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1384 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1385 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1386 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1387 self.dialog.vbox.pack_start(self.w_hbox3, False, False, 5)
1389 # commit required
1390 self.w_commit_required = gtk.CheckButton("Commit required")
1391 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1392 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1393 self.dialog.vbox.pack_start(self.w_commit_required, False, False, 5)
1395 # ifup required
1396 self.w_ifup_required = gtk.CheckButton("Ifup required")
1397 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1398 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1399 self.dialog.vbox.pack_start(self.w_ifup_required, False, False, 5)
1401 # table to use for layout of following command configurations
1402 self.cmds_table = gtk.Table(rows=4, columns=3)
1404 # ifconfig command
1405 self.w_ifconfig_cmd = gtk.Entry()
1406 self.w_ifconfig_cmd.set_width_chars(16)
1407 self.tooltips.set_tip(self.w_ifconfig_cmd, "The command to use to configure the network card")
1408 self.w_ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1409 self.w_ifconfig_cmd_label = gtk.Label("Network interface configure command")
1410 self.w_ifconfig_cmd_button = file_browse_button(self.dialog, self.w_ifconfig_cmd)
1411 self.cmds_table.attach(self.w_ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1412 self.cmds_table.attach(self.w_ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1413 self.cmds_table.attach(self.w_ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1415 # iwconfig command
1416 self.w_iwconfig_cmd = gtk.Entry()
1417 self.w_iwconfig_cmd.set_width_chars(16)
1418 self.tooltips.set_tip(self.w_iwconfig_cmd, "The command to use to configure the wireless connection")
1419 self.w_iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1420 self.w_iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1421 self.w_iwconfig_cmd_button = file_browse_button(self.dialog, self.w_iwconfig_cmd)
1422 self.cmds_table.attach(self.w_iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1423 self.cmds_table.attach(self.w_iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1424 self.cmds_table.attach(self.w_iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1426 # iwlist command
1427 self.w_iwlist_cmd = gtk.Entry()
1428 self.w_iwlist_cmd.set_width_chars(16)
1429 self.tooltips.set_tip(self.w_iwlist_cmd, "The command to use to scan for access points")
1430 self.w_iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1431 self.w_iwlist_cmd_label = gtk.Label("Wireless scanning command")
1432 self.w_iwlist_cmd_button = file_browse_button(self.dialog, self.w_iwlist_cmd)
1433 self.cmds_table.attach(self.w_iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1434 self.cmds_table.attach(self.w_iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1435 self.cmds_table.attach(self.w_iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1437 # route command
1438 self.w_route_cmd = gtk.Entry()
1439 self.w_route_cmd.set_width_chars(16)
1440 self.tooltips.set_tip(self.w_route_cmd, "The command to use to configure the network routing")
1441 self.w_route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1442 self.w_route_cmd_label = gtk.Label("Network route configure command")
1443 self.w_route_cmd_button = file_browse_button(self.dialog, self.w_route_cmd)
1444 self.cmds_table.attach(self.w_route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1445 self.cmds_table.attach(self.w_route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1446 self.cmds_table.attach(self.w_route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1448 self.dialog.vbox.pack_start(self.cmds_table, False, False, 5)
1450 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1452 #Parameters:
1454 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1456 # 'data' -- tuple - list of arbitrary arguments (not used)
1458 #Returns:
1460 # nothing
1461 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1462 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1464 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1466 #Parameters:
1468 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1470 # 'data' -- tuple - list of arbitrary arguments (not used)
1472 #Returns:
1474 # nothing
1475 def toggle_speak(self, speak_toggle, data=None):
1476 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1477 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1479 # Display preferences dialog and operate until canceled or okayed.
1481 #Parameters:
1483 # nothing
1485 #Returns:
1487 # integer -- gtk response ID
1488 def run(self):
1489 self.dialog.show_all()
1490 return self.dialog.run()
1492 # Write updated values to config file.
1494 #Parameters:
1496 # nothing
1498 #Returns:
1500 # nothing
1501 def save(self):
1502 print "saving prefs"
1503 if self.w_auto_detect.get_active():
1504 set_network_device("auto_detect")
1505 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1506 else:
1507 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1508 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1509 set_network_device(interface)
1510 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1511 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1512 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1513 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1514 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1515 self.confFile.set_opt('DEFAULT.ifconfig_command', self.w_ifconfig_cmd.get_text())
1516 self.confFile.set_opt('DEFAULT.iwconfig_command', self.w_iwconfig_cmd.get_text())
1517 self.confFile.set_opt('DEFAULT.iwlist_command', self.w_iwlist_cmd.get_text())
1518 self.confFile.set_opt('DEFAULT.route_command', self.w_route_cmd.get_text())
1520 # Remove preferences window.
1522 #Parameters:
1524 # nothing
1526 #Returns:
1528 # nothing
1529 def destroy(self):
1530 self.dialog.destroy()
1531 del self.dialog
1534 # Edit and return an AP profile.
1535 class profile_dialog:
1536 # Create a new profile_dialog.
1538 #Parameters:
1540 # 'parent' -- gtk.Object - Usually, the calling window.
1542 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1544 #Returns:
1546 # profile_dialog instance
1547 def __init__( self, parent, profile ):
1548 global wifi_radar_icon
1549 self.parent = parent
1550 self.profile = profile
1551 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1552 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1553 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1554 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1555 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1556 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1557 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1558 self.dialog.set_icon( icon )
1559 self.dialog.set_resizable( False )
1560 self.dialog.set_transient_for( self.parent.window )
1561 #self.dialog.set_size_request( 400, 400 )
1562 #################
1563 essid_table = gtk.Table( 1, 2, False )
1564 essid_table.set_row_spacings( 3 )
1565 essid_table.set_col_spacings( 3 )
1567 # The essid labels
1568 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1569 # The essid textboxes
1570 self.essid_entry = gtk.Entry( 32 )
1571 self.essid_entry.set_text( self.profile['essid'] )
1572 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1573 # Add the essid table to the dialog
1574 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1576 bssid_table = gtk.Table( 1, 2, False )
1577 bssid_table.set_row_spacings( 3 )
1578 bssid_table.set_col_spacings( 3 )
1579 # The bssid labels
1580 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1581 # The bssid textboxes
1582 self.bssid_entry = gtk.Entry( 32 )
1583 self.bssid_entry.set_text( self.profile['bssid'] )
1584 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1585 #self.key = gtk.Entry( 32 )
1586 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1587 # Add the bssid table to the dialog
1588 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1590 # create the wifi expander
1591 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1592 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1593 wifi_table = gtk.Table( 4, 2, False )
1594 wifi_table.set_row_spacings( 3 )
1595 wifi_table.set_col_spacings( 3 )
1596 # The Wifi labels
1597 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1598 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1599 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1600 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1601 # The Wifi text boxes
1602 self.mode_combo = gtk.combo_box_new_text()
1603 for mode in self.WIFI_MODES:
1604 self.mode_combo.append_text( mode )
1605 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1606 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1607 self.channel_combo = gtk.combo_box_new_text()
1608 for channel in self.WIFI_CHANNELS:
1609 self.channel_combo.append_text( channel )
1610 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1611 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1613 self.key_entry = gtk.Entry( 64 )
1614 self.key_entry.set_text( self.profile['key'] )
1615 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1617 self.security_combo = gtk.combo_box_new_text()
1618 for security in self.WIFI_SECURITY:
1619 self.security_combo.append_text( security )
1620 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1621 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1622 # Add the wifi table to the expander
1623 self.wifi_expander.add( wifi_table )
1624 # Add the expander to the dialog
1625 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1627 # create the wpa expander
1628 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1629 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1630 wpa_table = gtk.Table( 1, 2, False )
1631 wpa_table.set_row_spacings( 3 )
1632 wpa_table.set_col_spacings( 3 )
1633 # The labels
1634 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1635 # The text boxes
1636 self.wpa_driver_entry = gtk.Entry()
1637 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1638 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1639 # Add the wpa table to the expander
1640 self.wpa_expander.add( wpa_table )
1641 # Add the expander to the dialog
1642 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1644 # create the dhcp expander
1645 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1646 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1647 ip_table = gtk.Table( 6, 2, False )
1648 ip_table.set_row_spacings( 3 )
1649 ip_table.set_col_spacings( 3 )
1650 # The IP labels
1651 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1652 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1653 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1654 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1655 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1656 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1657 # The IP text boxes
1658 self.ip_entry = gtk.Entry( 15 )
1659 self.ip_entry.set_text( self.profile['ip'] )
1660 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1661 self.netmask_entry = gtk.Entry( 15 )
1662 self.netmask_entry.set_text( self.profile['netmask'] )
1663 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1664 self.gw_entry = gtk.Entry( 15 )
1665 self.gw_entry.set_text( self.profile['gateway'] )
1666 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1667 self.domain_entry = gtk.Entry( 32 )
1668 self.domain_entry.set_text( self.profile['domain'] )
1669 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1670 self.dns1_entry = gtk.Entry( 15 )
1671 self.dns1_entry.set_text( self.profile['dns1'] )
1672 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1673 self.dns2_entry = gtk.Entry( 15 )
1674 self.dns2_entry.set_text( self.profile['dns2'] )
1675 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1676 # Add the ip table to the expander
1677 self.dhcp_expander.add( ip_table )
1678 # Add the expander to the dialog
1679 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1681 # create the connection-building postpre expander
1682 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1683 con_pp_table = gtk.Table( 2, 2, False )
1684 con_pp_table.set_row_spacings( 3 )
1685 con_pp_table.set_col_spacings( 3 )
1686 # The labels
1687 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1688 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1689 # The text boxes
1690 self.con_prescript_entry = gtk.Entry()
1691 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1692 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1693 self.con_postscript_entry = gtk.Entry()
1694 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1695 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1696 # Add the pp table to the expander
1697 self.con_pp_expander.add( con_pp_table )
1698 # Add the expander to the dialog
1699 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1701 # create the disconnection postpre expander
1702 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1703 dis_pp_table = gtk.Table( 2, 2, False )
1704 dis_pp_table.set_row_spacings( 3 )
1705 dis_pp_table.set_col_spacings( 3 )
1706 # The labels
1707 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1708 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1709 # The text boxes
1710 self.dis_prescript_entry = gtk.Entry()
1711 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1712 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1713 self.dis_postscript_entry = gtk.Entry()
1714 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1715 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1716 # Add the pp table to the expander
1717 self.dis_pp_expander.add( dis_pp_table )
1718 # Add the expander to the dialog
1719 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1721 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1723 #Parameters:
1725 # nothing
1727 #Returns:
1729 # dictionary or None -- a profile, or None on cancel
1730 def run( self ):
1731 self.dialog.show_all()
1732 if self.dialog.run():
1733 self.profile['known'] = True
1734 self.profile['essid'] = self.essid_entry.get_text().strip()
1735 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1736 self.profile['key'] = self.key_entry.get_text().strip()
1737 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1738 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1739 self.profile['encrypted'] = ( self.profile['security'] != '' )
1740 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1741 self.profile['protocol'] = 'g'
1742 self.profile['available'] = ( self.profile['signal'] > 0 )
1743 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1744 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1745 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1746 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1747 # wpa
1748 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1749 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1750 # dhcp
1751 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1752 self.profile['ip'] = self.ip_entry.get_text().strip()
1753 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1754 self.profile['gateway'] = self.gw_entry.get_text().strip()
1755 self.profile['domain'] = self.domain_entry.get_text().strip()
1756 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1757 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1758 return self.profile
1759 return None
1761 # Remove profile dialog.
1763 #Parameters:
1765 # nothing
1767 #Returns:
1769 # nothing
1770 def destroy( self ):
1771 self.dialog.destroy()
1772 del self.dialog
1774 # Respond to expanding/hiding IP segment.
1776 #Parameters:
1778 # 'widget' -- gtk.Widget - The widget sending the event.
1780 # 'data' -- tuple - List of arbitrary arguments (not used)
1782 #Returns:
1784 # nothing
1785 def toggle_use_dhcp( self, widget, data = None ):
1786 expanded = self.dhcp_expander.get_expanded()
1787 if expanded:
1788 self.dhcp_expander.set_label( USE_IP_LABEL )
1789 else:
1790 self.dhcp_expander.set_label( USE_DHCP_LABEL )
1792 # Respond to expanding/hiding WPA segment.
1794 #Parameters:
1796 # 'widget' -- gtk.Widget - The widget sending the event.
1798 # 'data' -- tuple - List of arbitrary arguments (not used)
1800 #Returns:
1802 # nothing
1803 def toggle_use_wpa( self, widget, data = None ):
1804 expanded = self.wpa_expander.get_expanded()
1805 if expanded:
1806 self.wpa_expander.set_label( USE_WPA_LABEL )
1807 else:
1808 self.wpa_expander.set_label( NO_WPA_LABEL )
1810 # Return the index where item matches a cell in array.
1812 #Parameters:
1814 # 'item' -- string - Item to find in array
1816 # 'array' -- list - List in which to find match.
1818 #Returns:
1820 # integer - 0 (no match) or higher (index of match)
1821 def get_array_index( self, item, array ):
1822 try:
1823 return array.index( item.strip() )
1824 except:
1825 pass
1826 return 0
1828 # Return the value in array[ index ]
1830 #Parameters:
1832 # 'index' -- integer - The index to look up.
1834 # 'array' -- list - List in which to look up value.
1836 #Returns:
1838 # string -- empty string (no match) or looked up value
1839 def get_array_item( self, index, array ):
1840 try:
1841 return array[ index ]
1842 except:
1843 pass
1844 return ''
1846 # A simple class for putting up a "Please wait" dialog so the user
1847 # doesn't think we've forgotten about them. Implements the status interface.
1848 class StatusWindow:
1849 # Create a new StatusWindow.
1851 #Parameters:
1853 # 'parent' -- gtk.Object - Usually, the calling window.
1855 #Returns:
1857 # StatusWindow instance
1859 #NOTE:
1861 # Sample implementation of status interface. Status interface
1862 #requires .show(), .update_message(message), and .hide() methods.
1863 def __init__( self, parent ):
1864 global wifi_radar_icon
1865 self.parent = parent
1866 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
1867 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1868 self.dialog.set_icon( icon )
1869 self.lbl = gtk.Label("Please wait...")
1870 self.bar = gtk.ProgressBar()
1871 self.dialog.vbox.pack_start(self.lbl)
1872 self.dialog.vbox.pack_start(self.bar)
1873 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
1874 self.timer = None
1876 # Change the message displayed to the user.
1878 #Parameters:
1880 # 'message' -- string - The message to show to the user.
1882 #Returns:
1884 # nothing
1885 def update_message( self, message ):
1886 self.lbl.set_text(message)
1888 # Update the StatusWindow progress bar.
1890 #Parameters:
1892 # nothing
1894 #Returns:
1896 # True -- always return True
1897 def update_window( self ):
1898 self.bar.pulse()
1899 return True
1901 # Display and operate the StatusWindow.
1903 #Parameters:
1905 # nothing
1907 #Returns:
1909 # nothing
1910 def run( self ):
1911 pass
1913 # Show all the widgets of the StatusWindow.
1915 #Parameters:
1917 # nothing
1919 #Returns:
1921 # nothing
1922 def show( self ):
1923 self.dialog.show_all()
1924 self.timer = gobject.timeout_add(250, self.update_window)
1925 return False
1927 # Hide all the widgets of the StatusWindow.
1929 #Parameters:
1931 # nothing
1933 #Returns:
1935 # nothing
1936 def hide( self ):
1937 if self.timer:
1938 gobject.source_remove(self.timer)
1939 self.timer = None
1940 self.dialog.hide_all()
1941 return False
1943 # Remove the StatusWindow.
1945 #Parameters:
1947 # nothing
1949 #Returns:
1951 # nothing
1952 def destroy( self ):
1953 if self.timer:
1954 gobject.source_remove(self.timer)
1955 self.dialog.destroy()
1956 del self.dialog
1959 # Manage a GTK About Dialog
1960 class about_dialog(gtk.AboutDialog):
1961 # Subclass GTK AboutDialog
1963 #Parameters:
1965 # nothing
1967 #Returns:
1969 # nothing
1970 def __init__( self ):
1971 global wifi_radar_icon
1973 gtk.AboutDialog.__init__(self)
1974 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"])
1975 self.set_comments("WiFi connection manager")
1976 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
1977 self.set_documenters(["Gary Case"])
1978 license = """
1979 This program is free software; you can redistribute it and/or modify
1980 it under the terms of the GNU General Public License as published by
1981 the Free Software Foundation; either version 2 of the License, or
1982 (at your option) any later version.
1984 This program is distributed in the hope that it will be useful,
1985 but WITHOUT ANY WARRANTY; without even the implied warranty of
1986 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1987 GNU General Public License for more details.
1989 You should have received a copy of the GNU General Public License
1990 along with this program; if not, write to the Free Software
1991 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
1992 self.set_license(license)
1993 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1994 self.set_logo(logo)
1995 self.set_name("WiFi Radar")
1996 self.set_version(WIFI_RADAR_VERSION)
1997 self.set_website("http://developer.berlios.de/projects/wifi-radar/")
2001 # Manage the configuration for the application, including reading and writing the config from/to a file.
2002 class ConfigFile(ConfigParser.SafeConfigParser):
2003 # Create a new ConfigFile.
2005 #Parameters:
2007 # 'filename' -- string - The configuration file's name.
2009 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2011 #Returns:
2013 # ConfigFile instance
2014 def __init__( self, filename, defaults ):
2015 self.filename = filename
2016 self.auto_profile_order = []
2017 ConfigParser.SafeConfigParser.__init__(self, defaults)
2019 # Set the contents of a section to values from a dictionary.
2021 #Parameters:
2023 # 'section_name' -- string - Configuration file section.
2025 # 'section_dict' -- dictionary - Values to add to section.
2027 #Returns:
2029 # nothing
2030 def set_section( self, section_name, section_dict ):
2031 try:
2032 self.add_section(section_name)
2033 except ConfigParser.DuplicateSectionError:
2034 pass
2035 for key in section_dict.keys():
2036 if type(section_dict[key]) == BooleanType:
2037 self.set_bool_opt(section_name + "." + key, section_dict[key])
2038 elif type(section_dict[key]) == IntType:
2039 self.set_int_opt(section_name + "." + key, section_dict[key])
2040 elif type(section_dict[key]) == FloatType:
2041 self.set_float_opt(section_name + "." + key, section_dict[key])
2042 else:
2043 self.set_opt(section_name + "." + key, section_dict[key])
2045 # Return the profile recorded in the specified section.
2047 #Parameters:
2049 # 'section_name' -- string - Configuration file section.
2051 #Returns:
2053 # dictionary or None - The specified profile or None if not found
2054 def get_profile( self, section_name ):
2055 if section_name in self.profiles():
2056 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' ]
2057 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
2058 int_types = [ 'signal' ]
2059 profile = {}
2060 for option in bool_types:
2061 profile[option] = self.get_opt_as_bool( section_name + "." + option )
2062 for option in int_types:
2063 profile[option] = self.get_opt_as_int( section_name + "." + option )
2064 for option in str_types:
2065 profile[option] = self.get_opt( section_name + "." + option )
2066 return profile
2067 return None
2069 # Get a config option and handle exceptions.
2071 #Parameters:
2073 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2074 # period and the option key. (E.g. "DEFAULT.interface")
2076 #Returns:
2078 # string or None - option value as string or None on failure
2079 def get_opt( self, option_path ):
2080 #print "ConfigFile.get_opt: ", option_path
2081 (section, option) = option_path.split('.')
2082 try:
2083 return self.get(section, option)
2084 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2085 return None
2087 # Get a config option and return as a boolean type.
2089 #Parameters:
2091 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2092 # period and the option key. (E.g. "DEFAULT.interface")
2094 #Returns:
2096 # boolean - option value as boolean
2097 def get_opt_as_bool( self, option_path ):
2098 option = self.get_opt(option_path)
2099 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2100 return option
2101 if option == 'True':
2102 return True
2103 if option == 'False':
2104 return False
2105 raise ValueError, 'boolean option was not True or False'
2107 # Get a config option and return as an integer type.
2109 #Parameters:
2111 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2112 # period and the option key. (E.g. "DEFAULT.interface")
2114 #Returns:
2116 # integer- option value as integer
2117 def get_opt_as_int( self, option_path ):
2118 return int(float(self.get_opt(option_path)))
2120 # Convert boolean type to string and set config option.
2122 #Parameters:
2124 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2125 # period and the option key. (E.g. "DEFAULT.interface")
2127 # 'value' -- boolean - Value to set.
2129 #Returns:
2131 # nothing
2132 def set_bool_opt( self, option_path, value ):
2133 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2134 value == 'True'
2135 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2136 value == 'False'
2137 else:
2138 raise ValueError, 'cannot convert value to string'
2139 self.set_opt(option_path, repr(value))
2141 # Convert integer type to string and set config option.
2143 #Parameters:
2145 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2146 # period and the option key. (E.g. "DEFAULT.interface")
2148 # 'value' -- integer - Value to set.
2150 #Returns:
2152 # nothing
2153 def set_int_opt( self, option_path, value ):
2154 if not isinstance(value, IntType):
2155 raise ValueError, 'value is not an integer'
2156 self.set_opt(option_path, repr(value))
2158 # Convert float type to string and set config option.
2160 #Parameters:
2162 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2163 # period and the option key. (E.g. "DEFAULT.interface")
2165 # 'value' -- float - Value to set.
2167 #Returns:
2169 # nothing
2170 def set_float_opt( self, option_path, value ):
2171 if not isinstance(value, FloatType):
2172 raise ValueError, 'value is not a float'
2173 self.set_opt(option_path, repr(int(value)))
2175 # Set a config option while handling exceptions.
2177 #Parameters:
2179 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2180 # period and the option key. (E.g. "DEFAULT.interface")
2182 # 'value' -- string - Value to set.
2184 #Returns:
2186 # nothing
2187 def set_opt( self, option_path, value ):
2188 (section, option) = option_path.split('.')
2189 try:
2190 self.set(section, option, value)
2191 except ConfigParser.NoSectionError:
2192 self.add_section(section)
2193 self.set_opt(option_path, value)
2195 # Return a list of the section names which denote AP profiles.
2197 #Parameters:
2199 # nothing
2201 #Returns:
2203 # list - profile names
2204 def profiles( self ):
2205 profile_list = []
2206 for section in self.sections():
2207 if ':' in section:
2208 profile_list.append(section)
2209 return profile_list
2211 # Read configuration file from disk into instance variables.
2213 #Parameters:
2215 # nothing
2217 #Returns:
2219 # nothing
2220 def read( self ):
2221 fp = open( self.filename, "r" )
2222 self.readfp(fp)
2223 # convert the auto_profile_order to a list for ordering
2224 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2225 for ap in self.profiles():
2226 self.set_bool_opt( ap + '.known', True)
2227 if ap in self.auto_profile_order: continue
2228 self.auto_profile_order.append( ap )
2230 # Write configuration file to disk from instance variables. Copied from
2231 # ConfigParser and modified to write options in alphabetical order.
2233 #Parameters:
2235 # nothing
2237 #Returns:
2239 # nothing
2240 def write( self ):
2241 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2242 fp = open( self.filename, "w" )
2243 # write DEFAULT section first
2244 if self._defaults:
2245 fp.write("[DEFAULT]\n")
2246 for key in sorted(self._defaults.keys()):
2247 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2248 fp.write("\n")
2249 # write non-profile sections first
2250 for section in self._sections:
2251 if section not in self.profiles():
2252 fp.write("[%s]\n" % section)
2253 for key in sorted(self._sections[section].keys()):
2254 if key != "__name__":
2255 fp.write("%s = %s\n" %
2256 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2257 fp.write("\n")
2258 # write profile sections
2259 for section in self._sections:
2260 if section in self.profiles():
2261 fp.write("[%s]\n" % section)
2262 for key in sorted(self._sections[section].keys()):
2263 if key != "__name__":
2264 fp.write("%s = %s\n" %
2265 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2266 fp.write("\n")
2270 # Load our conf file and known profiles
2271 # Defaults, these may get overridden by values found in the conf file.
2272 config_defaults = { # The network interface you use.
2273 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2274 'interface': "auto_detect",
2275 # How long should the scan for access points last?
2276 'scan_timeout': '5',
2277 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2278 # Set the speak_up option to false if you do not have or want this.
2279 'speak_command': '/usr/bin/say',
2280 # Should I speak up when connecting to a network? (If you have a speech command)
2281 'speak_up': 'False',
2282 # You may set this to true for cards that require a "commit" command with iwconfig
2283 'commit_required': 'False',
2284 # You may set this to true for cards that require the interface to be brought up first
2285 'ifup_required': 'False',
2286 # Set the location of several important programs
2287 'iwlist_command': '/sbin/iwlist',
2288 'iwconfig_command': '/sbin/iwconfig',
2289 'ifconfig_command': '/sbin/ifconfig',
2290 'route_command': '/sbin/route',
2291 'auto_profile_order': '[]',
2292 'version': WIFI_RADAR_VERSION }
2294 config_dhcp = { # DHCP client
2295 'command': 'dhcpcd',
2296 # How long to wait for an IP addr from DHCP server
2297 'timeout': '30',
2298 # Arguments to use with DHCP client on connect
2299 'args': '-D -o -i dhcp_client -t %(timeout)s',
2300 # Argument to use with DHCP client on disconnect
2301 'kill_args': '-k',
2302 # The file where DHCP client PID is written
2303 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2305 config_wpa = { # WPA Supplicant
2306 'command': '/usr/sbin/wpa_supplicant',
2307 # Arguments to use with WPA Supplicant on connect
2308 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2309 # Arguments to use with WPA Supplicant on disconnect
2310 'kill_command': '',
2311 # Where the WPA Supplicant config file can be found
2312 'configuration': '/etc/wpa_supplicant.conf',
2313 # Driver to use with WPA Supplicant
2314 'driver': 'wext',
2315 # The file where WPA Supplicant PID is written
2316 'pidfile': '/var/run/wpa_supplicant.pid' }
2318 # initialize config, with defaults
2319 confFile = ConfigFile(CONF_FILE, config_defaults)
2320 confFile.set_section("DHCP", config_dhcp)
2321 confFile.set_section("WPA", config_wpa)
2323 if not os.path.isfile( CONF_FILE ):
2324 confFile.set_bool_opt('DEFAULT.new_file', True)
2325 else:
2326 if not os.access(CONF_FILE, os.R_OK):
2327 print "Can't open " + CONF_FILE + "."
2328 print "Are you root?"
2329 sys.exit()
2330 confFile.read()
2333 ####################################################################################################
2334 # Embedded Images
2335 wifi_radar_icon = [ ""
2336 "GdkP"
2337 "\0\0\22""7"
2338 "\2\1\0\2"
2339 "\0\0\1\214"
2340 "\0\0\0c"
2341 "\0\0\0O"
2342 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2343 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2344 "\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"
2345 "\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"
2346 "\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"
2347 "\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"
2348 "\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"
2349 "\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"
2350 "\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"
2351 "\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"
2352 "\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"
2353 "\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"
2354 "\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"
2355 "\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"
2356 "\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"
2357 "\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"
2358 "\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"
2359 "\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"
2360 "\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"
2361 "\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"
2362 "\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"
2363 "\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"
2364 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2365 "\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"
2366 "\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"
2367 "\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"
2368 "\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"
2369 "\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"
2370 "\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"
2371 "\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"
2372 "\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"
2373 "\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"
2374 "\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"
2375 "\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"
2376 "\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"
2377 "\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"
2378 "\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"
2379 "\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"
2380 "\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"
2381 "\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"
2382 "\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"
2383 "\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"
2384 "\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"
2385 "\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"
2386 "\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"
2387 "\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"
2388 "\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"
2389 "\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"
2390 "\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"
2391 "\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"
2392 "\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"
2393 "\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"
2394 "\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"
2395 "\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"
2396 "\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"
2397 "\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"
2398 "\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"
2399 "\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"
2400 "\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"
2401 "\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"
2402 "\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"
2403 "\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"
2404 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2405 "\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"
2406 "\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"
2407 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2408 "\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"
2409 "\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"
2410 "\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"
2411 "\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"
2412 "\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"
2413 "\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"
2414 "\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"
2415 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2416 "\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"
2417 "\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"
2418 "\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"
2419 "\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"
2420 "\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"
2421 "\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"
2422 "|\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"
2423 "\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"
2424 "\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"
2425 "\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"
2426 "\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"
2427 "\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"
2428 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2429 "\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"
2430 "\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"
2431 "\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"
2432 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2433 "\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"
2434 "\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"
2435 "\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"
2436 "\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"
2437 "\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"
2438 "\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"
2439 "\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"
2440 "\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"
2441 "\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"
2442 "\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"
2443 "\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"
2444 "\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"
2445 "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"
2446 "\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"
2447 "\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"
2448 "\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"
2449 "\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"
2450 "\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"
2451 "\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"
2452 "\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"
2453 "\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"
2454 "\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"
2455 "\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"
2456 "\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"
2457 "\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"
2458 "\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"
2459 "\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"
2460 "\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|"
2461 "\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"
2462 "\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"
2463 "\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"
2464 "\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"
2465 "\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"
2466 "\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"
2467 "\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"
2468 "\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"
2469 "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"
2470 "\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"
2471 "\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"
2472 "\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"
2473 "\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"
2474 "\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"
2475 "\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"
2476 "\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"
2477 "\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"
2478 "\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"
2479 "\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"
2480 "\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"
2481 "\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"
2482 "\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"
2483 "\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"
2484 "\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"
2485 "\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"
2486 "\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"
2487 "\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"
2488 "\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"
2489 "\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"
2490 "\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"
2491 "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"
2492 "\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"
2493 "\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"
2494 "\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"
2495 "\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"
2496 "\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"
2497 "\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"
2498 "\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"
2499 "\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"
2500 "\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"
2501 "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"
2502 "\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"
2503 "\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"
2504 "\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"
2505 "\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"
2506 "\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"
2507 "\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"
2508 "\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"
2509 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2510 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2511 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2512 "\0"]
2514 known_profile_icon = [ ""
2515 "GdkP"
2516 "\0\0\5""0"
2517 "\2\1\0\2"
2518 "\0\0\0P"
2519 "\0\0\0\24"
2520 "\0\0\0\24"
2521 "\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"
2522 "\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"
2523 "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"
2524 "\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"
2525 "\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"
2526 "\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"
2527 "\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"
2528 "\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"
2529 "\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"
2530 "\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"
2531 "\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"
2532 "\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"
2533 "\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"
2534 "\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"
2535 "\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"
2536 "\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"
2537 "\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"
2538 "\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"
2539 "\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"
2540 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2541 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2542 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2543 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2544 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2545 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2546 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2547 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2548 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2549 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2550 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2551 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2552 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2553 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2554 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2555 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2556 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2557 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2558 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2559 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2560 "\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"
2561 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2562 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2563 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2564 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2565 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2566 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2567 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2568 "\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"
2569 "\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"
2570 "\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"
2571 "\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"
2572 "\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"]
2574 unknown_profile_icon = [ ""
2575 "GdkP"
2576 "\0\0\5\22"
2577 "\2\1\0\2"
2578 "\0\0\0P"
2579 "\0\0\0\24"
2580 "\0\0\0\24"
2581 "\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"
2582 "\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"
2583 "\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"
2584 "\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"
2585 "(\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"
2586 "\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"
2587 "#\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"
2588 "\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"
2589 "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"
2590 "\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"
2591 "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"
2592 "\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"
2593 "\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"
2594 "\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"
2595 "\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"
2596 "\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"
2597 "\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"
2598 "\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"
2599 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2600 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2601 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2602 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2603 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2604 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2605 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2606 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2607 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2608 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2609 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2610 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2611 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2612 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2613 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2614 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2615 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2616 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2617 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2618 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2619 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2620 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2621 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2622 "\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"
2623 "\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"
2624 "\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"
2625 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2627 signal_xpm_barely = [
2628 "20 20 10 1",
2629 " c None",
2630 ". c #C6C6C6",
2631 "+ c #CCCCCC",
2632 "@ c #DBDBDB",
2633 "# c #D3D3D3",
2634 "$ c #A9B099",
2635 "% c #95A173",
2636 "& c #6B8428",
2637 "* c #B4B7AC",
2638 "= c #80924D",
2639 " .+++.",
2640 " +@@@+",
2641 " +@@@+",
2642 " +@@@+",
2643 " +@@@+",
2644 " .++++#@@@+",
2645 " +@@@@@@@@+",
2646 " +@@@@@@@@+",
2647 " +@@@@@@@@+",
2648 " +@@@@@@@@+",
2649 " $%%%%#@@@@@@@@+",
2650 " %&&&&@@@@@@@@@+",
2651 " %&&&&@@@@@@@@@+",
2652 " %&&&&@@@@@@@@@+",
2653 " %&&&&@@@@@@@@@+",
2654 "*%%%%=&&&&@@@@@@@@@+",
2655 "%&&&&&&&&&@@@@@@@@@+",
2656 "%&&&&&&&&&@@@@@@@@@+",
2657 "%&&&&&&&&&@@@@@@@@@+",
2658 "*%%%%%%%%%+++++++++."
2662 signal_xpm_best = [
2663 "20 20 6 1",
2664 " c None",
2665 ". c #9DAABF",
2666 "+ c #7B96BF",
2667 "@ c #386EBF",
2668 "# c #5982BF",
2669 "$ c #AEB4BF",
2670 " .+++.",
2671 " +@@@+",
2672 " +@@@+",
2673 " +@@@+",
2674 " +@@@+",
2675 " .++++#@@@+",
2676 " +@@@@@@@@+",
2677 " +@@@@@@@@+",
2678 " +@@@@@@@@+",
2679 " +@@@@@@@@+",
2680 " .++++#@@@@@@@@+",
2681 " +@@@@@@@@@@@@@+",
2682 " +@@@@@@@@@@@@@+",
2683 " +@@@@@@@@@@@@@+",
2684 " +@@@@@@@@@@@@@+",
2685 "$++++#@@@@@@@@@@@@@+",
2686 "+@@@@@@@@@@@@@@@@@@+",
2687 "+@@@@@@@@@@@@@@@@@@+",
2688 "+@@@@@@@@@@@@@@@@@@+",
2689 "$++++++++++++++++++."
2692 signal_xpm_none = [
2693 "20 20 6 1",
2694 " c None",
2695 ". c #C6C6C6",
2696 "+ c #CCCCCC",
2697 "@ c #DBDBDB",
2698 "# c #D3D3D3",
2699 "$ c #C2C2C2",
2700 " .+++.",
2701 " +@@@+",
2702 " +@@@+",
2703 " +@@@+",
2704 " +@@@+",
2705 " .++++#@@@+",
2706 " +@@@@@@@@+",
2707 " +@@@@@@@@+",
2708 " +@@@@@@@@+",
2709 " +@@@@@@@@+",
2710 " .++++#@@@@@@@@+",
2711 " +@@@@@@@@@@@@@+",
2712 " +@@@@@@@@@@@@@+",
2713 " +@@@@@@@@@@@@@+",
2714 " +@@@@@@@@@@@@@+",
2715 "$++++#@@@@@@@@@@@@@+",
2716 "+@@@@@@@@@@@@@@@@@@+",
2717 "+@@@@@@@@@@@@@@@@@@+",
2718 "+@@@@@@@@@@@@@@@@@@+",
2719 "$++++++++++++++++++."
2722 signal_xpm_ok = [
2723 "20 20 10 1",
2724 " c None",
2725 ". c #C6C6C6",
2726 "+ c #CCCCCC",
2727 "@ c #DBDBDB",
2728 "# c #A1A5B2",
2729 "$ c #848DA5",
2730 "% c #D3D3D3",
2731 "& c #4A5B8C",
2732 "* c #677498",
2733 "= c #B0B2B8",
2734 " .+++.",
2735 " +@@@+",
2736 " +@@@+",
2737 " +@@@+",
2738 " +@@@+",
2739 " #$$$$%@@@+",
2740 " $&&&&@@@@+",
2741 " $&&&&@@@@+",
2742 " $&&&&@@@@+",
2743 " $&&&&@@@@+",
2744 " #$$$$*&&&&@@@@+",
2745 " $&&&&&&&&&@@@@+",
2746 " $&&&&&&&&&@@@@+",
2747 " $&&&&&&&&&@@@@+",
2748 " $&&&&&&&&&@@@@+",
2749 "=$$$$*&&&&&&&&&@@@@+",
2750 "$&&&&&&&&&&&&&&@@@@+",
2751 "$&&&&&&&&&&&&&&@@@@+",
2752 "$&&&&&&&&&&&&&&@@@@+",
2753 "=$$$$$$$$$$$$$$++++."
2757 signal_xpm_low = [
2758 "20 20 8 1",
2759 " c None",
2760 ". c #C6C6C6",
2761 "+ c #CCCCCC",
2762 "@ c #DBDBDB",
2763 "# c #D3D3D3",
2764 "$ c #BFB0B5",
2765 "% c #C18799",
2766 "& c #C54F74",
2767 " .+++.",
2768 " +@@@+",
2769 " +@@@+",
2770 " +@@@+",
2771 " +@@@+",
2772 " .++++#@@@+",
2773 " +@@@@@@@@+",
2774 " +@@@@@@@@+",
2775 " +@@@@@@@@+",
2776 " +@@@@@@@@+",
2777 " .++++#@@@@@@@@+",
2778 " +@@@@@@@@@@@@@+",
2779 " +@@@@@@@@@@@@@+",
2780 " +@@@@@@@@@@@@@+",
2781 " +@@@@@@@@@@@@@+",
2782 "$%%%%#@@@@@@@@@@@@@+",
2783 "%&&&&@@@@@@@@@@@@@@+",
2784 "%&&&&@@@@@@@@@@@@@@+",
2785 "%&&&&@@@@@@@@@@@@@@+",
2786 "$%%%%++++++++++++++."
2790 ####################################################################################################
2791 # Make so we can be imported
2792 if __name__ == "__main__":
2793 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
2794 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
2795 else:
2796 import gtk, gobject
2797 gtk.gdk.threads_init()
2798 apQueue = Queue.Queue(100)
2799 commQueue = Queue.Queue(2)
2800 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue ) ).start()
2801 main_radar_window = radar_window(confFile, apQueue, commQueue)
2802 gobject.timeout_add( 500, main_radar_window.update_plist_items )
2803 main_radar_window.main()