Add a text entry to specify the ifconfig command
[wifi-radar.git] / wifi-radar
blobc48bbcef01c44b223ff0dafe81c283a873ba52a1
1 #!/usr/bin/python
3 # $Id$
4 # vi:set filetype=python noet:
6 # A wireless profile manager for Linux
8 # Originally created for x1000 Linux:
9 # http://x1000.bitbuilder.com
11 # Created by:
12 # Ahmad Baitalmal <ahmad@baitalmal.com>
14 # Maintained by:
15 # Brian Elliott Finley <brian@thefinleys.com>
17 # License:
18 # GPL
20 # http://www.bitbuilder.com/wifi_radar
21 # http://svn.systemimager.org
23 # See CREDITS file for more contributors.
24 # See ChangeLog file for, well, changes.
27 import ConfigParser, os, Queue, re, string, sys, threading
28 from signal import SIGTERM
29 from subprocess import call, Popen, PIPE
30 from time import sleep
31 from types import *
33 WIFI_RADAR_VERSION = "0.0.0"
35 if __debug__:
36 print '__debug__ is True'
37 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
38 # turn on debugging.
41 # Where the conf file should live could be different for your distro. Please change
42 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
44 CONF_FILE = "/etc/wifi-radar.conf"
46 os.environ['LC_MESSAGES'] = 'C'
49 #####################################
50 # Labels
51 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
52 USE_IP_LABEL = "Manual network configuration"
53 WIFI_SET_LABEL = "WiFi Options"
54 CON_PP_LABEL = "Connection Commands"
55 DIS_PP_LABEL = "Disconnection Commands"
56 USE_WPA_LABEL = "Use WPA"
57 NO_WPA_LABEL = "No WPA"
58 ####################################################################################################
60 ####################################################################################################
61 ####################################################################################################
63 # Sets the interface to the specified network device
65 #Parameters:
67 # 'device' -- string - The network device to use
69 #Returns:
71 # nothing
72 def set_network_device( device ):
73 #print "set_network_device: ", device
74 if device != "auto_detect":
75 confFile.set_opt('DEFAULT.interface', device)
76 else: # auto detect network device
77 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
78 # If no devices are found, default to eth1.
79 # call iwconfig command and read output
80 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
81 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
82 if len(wireless_devices) > 0:
83 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
84 #else:
85 #print "No wifi-device found. Exiting."
86 #sys.exit()
88 # Return a blank profile
90 #Parameters:
92 # none
94 #Returns:
96 # dictionary -- An AP profile with defaults set.
97 def get_new_profile():
98 return { 'known': False,
99 'available': False,
100 'encrypted': False,
101 'essid': '',
102 'bssid': '',
103 'protocol': 'g',
104 'signal': 0,
105 'channel': 'auto',
106 'con_prescript': '',
107 'con_postscript': '',
108 'dis_prescript': '',
109 'dis_postscript': '',
110 'key': '',
111 'mode': '',
112 'security': '',
113 'use_wpa': False,
114 'wpa_driver': '',
115 'use_dhcp': True,
116 'ip': '',
117 'netmask': '',
118 'gateway': '',
119 'domain': '',
120 'dns1': '',
121 'dns2': ''
124 # Combine essid and bssid to make a config file section name
126 #Parameters:
128 # 'essid' -- string - AP ESSID
130 # 'bssid' -- string - AP BSSID
132 #Returns:
134 # string -- the bssid concatenated to a colon, concatenated to the essid
135 def make_section_name( essid, bssid ):
136 return essid + ':' + bssid
138 # Split a config file section name into an essid and a bssid
140 #Parameters:
142 # 'section' -- string - Config file section name
144 #Returns:
146 # list -- the essid and bssid
147 def split_section_name( section ):
148 parts = re.split(':', section)
149 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
151 # Run commands through the shell
153 #Parameters:
155 # 'command' -- tuple - The command and arguments to run
157 #Returns:
159 # boolean -- True on success, otherwise, False
160 def shellcmd( command, environment = None ):
161 try:
162 env_tmp = os.environ
163 env_tmp.update(environment)
164 command = ' '.join(command)
165 return_code = call(command, shell=True, env=env_tmp)
166 if return_code >= 0:
167 return True
168 else:
169 print >>sys.stderr, "Child was terminated by signal", -return_code
170 except OSError, exception:
171 print >>sys.stderr, "Execution failed:", exception
172 return False
174 # Speak feedback message to user
176 #Parameters:
178 # 'words' -- string - Message to speak to user
180 #Returns:
182 # nothing
183 def say( words ):
184 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
185 words = words.replace( "\"", "\\\"" )
186 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
188 # Scan for a limited time and return AP names and bssid found.
189 # Access points we find will be put on the outgoing Queue, apQueue.
191 #Parameters:
193 # 'confFile' -- ConfigFile - Config file object
195 # 'apQueue' -- Queue - Queue on which to put AP profiles
197 # 'commandQueue' -- Queue - Queue from which to read commands
199 #Returns:
201 # nothing
202 def scanning_thread( confFile, apQueue, commandQueue ):
203 # Setup our essid pattern matcher
204 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
205 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abg]+)", re.I | re.M | re.S )
206 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
207 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
208 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
209 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
211 access_points = {}
212 command = "scan"
213 while True:
214 try:
215 command = commandQueue.get_nowait()
216 if __debug__: print "scanning_thread received command:", command
217 command_read = True
218 except Queue.Empty:
219 command_read = False
220 if command == "scan":
221 if __debug__: print "Beginning scan pass"
222 # Some cards need to have the interface up to scan
223 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
224 # call ifconfig command and wait for return
225 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
226 # update the signal strengths
227 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
228 #if __debug__:
229 #print "Current IP ", get_current_ip()
230 #print "Current ESSID ", get_current_essid()
231 #print "Current BSSID ", get_current_bssid()
232 # zero out the signal levels for all access points
233 for bssid in access_points:
234 access_points[bssid]['signal'] = 0
235 # split the scan data based on the address line
236 hits = scandata.split(' - ')
237 for hit in hits:
238 # set the defaults for profile template
239 profile = get_new_profile()
240 m = essid_pattern.search( hit )
241 if m:
242 # we found an essid
243 profile['essid'] = m.groups()[1]
244 m = bssid_pattern.search( hit ) # get BSSID from scan
245 if m: profile['bssid'] = m.groups()[1]
246 m = protocol_pattern.search( hit ) # get protocol from scan
247 if m: profile['protocol'] = m.groups()[1]
248 m = mode_pattern.search( hit ) # get mode from scan
249 if m: profile['mode'] = m.groups()[1]
250 m = channel_pattern.search( hit ) # get channel from scan
251 if m: profile['channel'] = m.groups()[1]
252 m = enckey_pattern.search( hit ) # get encryption key from scan
253 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
254 m = signal_pattern.search( hit ) # get signal strength from scan
255 if m: profile['signal'] = m.groups()[1]
256 access_points[ profile['bssid'] ] = profile
257 for bssid in access_points:
258 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
259 # Put all, now or previously, sensed access_points into apQueue
260 try:
261 apQueue.put_nowait( access_points[bssid] )
262 except Queue.Full:
263 pass
264 elif command == "exit":
265 if __debug__: print "Exiting scanning_thread"
266 return
267 if command_read: commandQueue.task_done()
268 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
269 sleep( 3 )
270 else:
271 sleep( 1 )
274 # Manage a connection; including reporting connection state,
275 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
276 class ConnectionManager():
277 # Create a new connection manager which can read a config file and send to scanning thread
278 # command Queue. A new manager checks for a pre-existing connection and takes
279 # its AP profile from the ESSID and BSSID to which it is currently attached.
281 #Parameters:
283 # 'confFile' -- ConfigFile - Config file object
285 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
287 #Returns:
289 # ConnectionManager instance
290 def __init__( self, confFile, commandQueue ):
291 self.confFile = confFile
292 self.commQueue = commandQueue
293 # is connection running?
294 self.state = False
295 if self.get_current_ip():
296 self.state = True
297 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
299 # Change the interface state: up or down.
301 #Parameters:
303 # 'state' -- string - The state to which to change the interface.
305 #Returns:
307 # nothing
308 def if_change( self, state ):
309 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
310 # call ifconfig command and wait for return
311 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
313 # Connect to the specified AP.
315 #Parameters:
317 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
319 # 'status_window' -- status_window - Instance of window to use to update user.
321 #Returns:
323 # nothing
324 def connect_to_network( self, profile, status_window ):
325 self.profile = profile
326 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
327 say( msg )
328 if __debug__: print " %s" % msg
329 # ready to dance
330 # Let's run the connection prescript
331 if self.profile['con_prescript'].strip() != '':
332 # got something to execute
333 # run connection prescript through shell and wait for return
334 if __debug__: print "executing connection prescript:", self.profile['con_prescript']
335 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
336 if status_window:
337 status_window.run()
338 # Some cards need to have the interface up
339 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
340 self.if_change('up')
341 # Start building iwconfig command line, command
342 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
343 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
344 # Setting essid
345 iwconfig_command.append( 'essid' )
346 iwconfig_command.append( self.profile['essid'] )
347 # Setting nick
348 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
349 # Setting key
350 iwconfig_command.append( 'key' )
351 if self.profile['key'] == '':
352 iwconfig_command.append( 'off' )
353 else:
354 iwconfig_command.append( self.profile['key'] )
355 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
356 # Setting mode to Master, this will cause association with the AP to fail
357 #iwconfig_command.append( 'mode' )
358 #iwconfig_command.append( 'Master' )
359 # Setting channel
360 if self.profile['channel'] != '':
361 iwconfig_command.append( 'channel' )
362 iwconfig_command.append( self.profile['channel'] )
363 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
364 iwconfig_command.append( 'ap' )
365 iwconfig_command.append( self.profile['bssid'] )
366 # Some cards require a commit
367 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
368 if __debug__: print 'iwconfig_args %s ' % ( iwconfig_args, )
369 iwconfig_command.append( 'commit' )
370 # call iwconfig command and wait for return
371 if not shellcmd(iwconfig_command): return
372 # Now normal network stuff
373 # Kill off any existing DHCP clients running
374 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
375 if __debug__: print "Killing existing DHCP..."
376 try:
377 if self.confFile.get_opt('DHCP.kill_args') != '':
378 # call DHCP client kill command and wait for return
379 if __debug__: print self.confFile.get_opt('DHCP.command') + " " + self.confFile.get_opt('DHCP.kill_args')
380 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
381 else:
382 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
383 except OSError:
384 print "failed to kill DHCP client"
385 sys.exit()
386 finally:
387 print "Stale pid file. Removing..."
388 os.remove(self.confFile.get_opt('DHCP.pidfile'))
389 # Kill off any existing WPA supplicants running
390 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
391 if __debug__: print "Killing existing WPA supplicant..."
392 try:
393 if not self.confFile.get_opt('WPA.kill_command') != '':
394 # call WPA supplicant kill command and wait for return
395 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
396 else:
397 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
398 except OSError:
399 print "failed to kill WPA supplicant"
400 sys.exit()
401 finally:
402 print "Stale pid file. Removing..."
403 os.remove(self.confFile.get_opt('WPA.pidfile'))
404 # Begin WPA supplicant
405 if self.profile['use_wpa'] :
406 if __debug__: print "WPA args: %s" % ( wpa_options, )
407 if status_window:
408 status_window.update_message("WPA supplicant starting")
409 # call WPA supplicant command and do not wait for return
410 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
411 if self.profile['use_dhcp'] :
412 if __debug__: print "Disable iwlist while dhcp in progress..."
413 try:
414 self.commQueue.put("pause")
415 except Queue.Full:
416 pass
417 if status_window:
418 status_window.update_message("Acquiring IP Address (DHCP)")
419 # call DHCP client command and do not wait for return
420 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
421 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
422 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
423 dhcp_proc = Popen(dhcp_command, stdout=None)
424 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
425 tick = 0.25
426 waiting = dhcp_proc.poll()
427 while waiting == None:
428 waiting = dhcp_proc.poll()
429 if timer < 0:
430 os.kill(dhcp_proc.pid, SIGTERM)
431 break
432 if sys.modules.has_key("gtk"):
433 while gtk.events_pending():
434 gtk.main_iteration(False)
435 timer -= tick
436 sleep(tick)
437 if not self.get_current_ip():
438 if status_window:
439 status_window.update_message("Could not get IP address!")
440 if sys.modules.has_key("gtk"):
441 while gtk.events_pending():
442 gtk.main_iteration(False)
443 sleep(3)
444 else:
445 print "Could not get IP address!"
446 self.disconnect_interface()
447 else:
448 if status_window:
449 status_window.update_message("Got IP address. Done.")
450 self.state = True
451 if sys.modules.has_key("gtk"):
452 while gtk.events_pending():
453 gtk.main_iteration(False)
454 sleep(2)
455 # Re-enable iwlist
456 try:
457 self.commQueue.put("scan")
458 except Queue.Full:
459 pass
460 else:
461 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'] )
462 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
463 resolv_contents = ''
464 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
465 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
466 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
467 if ( resolv_contents != '' ):
468 resolv_file=open('/etc/resolv.conf', 'w')
469 resolv_file.write(s)
470 resolv_file.close
471 if not shellcmd([ifconfig_command]): return
472 if not shellcmd([route_command]): return
473 self.state = True
474 # Let's run the connection postscript
475 con_postscript = self.profile['con_postscript']
476 if self.profile['con_postscript'].strip() != '':
477 if __debug__: print "executing connection postscript:", self.profile['con_postscript']
478 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')})
479 if status_window:
480 status_window.destroy()
482 # Disconnect from the AP with which a connection has been established/attempted.
484 #Parameters:
486 # nothing
488 #Returns:
490 # nothing
491 def disconnect_interface( self ):
492 msg = "Disconnecting"
493 say( msg )
494 if __debug__: print msg
495 # Pause scanning while manipulating card
496 try:
497 self.commQueue.put("pause")
498 except Queue.Full:
499 pass
500 if self.state:
501 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
502 # Let's run the disconnection prescript
503 if self.profile['dis_prescript'].strip() != '':
504 if __debug__: print "executing disconnection prescript:", self.profile['dis_prescript']
505 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')})
506 if __debug__: print "Kill off any existing DHCP clients running..."
507 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
508 if __debug__: print "Killing existing DHCP..."
509 try:
510 if self.confFile.get_opt('DHCP.kill_args') != '':
511 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
512 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
513 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
514 if __debug__: print "DHCP command", dhcp_command
515 # call DHCP client command and wait for return
516 if not shellcmd(dhcp_command): return
517 else:
518 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
519 except OSError:
520 print "failed to kill DHCP client"
521 if __debug__: print "Kill off any existing WPA supplicants running..."
522 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
523 if __debug__: print "Killing existing WPA supplicant..."
524 try:
525 if not self.confFile.get_opt('WPA.kill_command') != '':
526 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
527 if not shellcmd(wpa_command): return
528 else:
529 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
530 except OSError:
531 print "failed to kill WPA supplicant"
532 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
533 if __debug__: print "Let's clear out the wireless stuff"
534 if __debug__: print 'Now take the interface down'
535 self.if_change('down')
536 if __debug__: print 'Since it may be brought back up by the next scan, lets unset its IP'
537 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
538 # Let's run the disconnection postscript
539 if self.profile['dis_postscript'].strip() != '':
540 if __debug__: print "executing disconnection postscript:", self.profile['dis_postscript']
541 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
542 self.state = False
543 if __debug__: print 'Disconnect complete.'
544 # Begin scanning again
545 try:
546 self.commQueue.put("scan")
547 except Queue.Full:
548 pass
550 # Returns the current IP, if any, by calling ifconfig.
552 #Parameters:
554 # nothing
556 #Returns:
558 # string or None -- the IP address or None (if no there is no current connection)
559 def get_current_ip( self ):
560 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
561 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
562 # Be careful to the language (inet adr: in French for example)
564 # Hi Brian
566 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
567 # There the string in ifconfig is inet Adresse for the IP which isn't
568 # found by the current get_current_ip function in wifi-radar. I changed
569 # the according line (#289; gentoo, v1.9.6-r1) to
570 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
571 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
573 # I'd be happy if you could incorporate this small change because as now
574 # I've got to change the file every time it is updated.
576 # Best wishes
578 # Simon
579 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
580 line = ifconfig_info.read()
581 if ip_re.search( line ):
582 return ip_re.search( line ).group(1)
583 return None
585 # Returns the current ESSID, if any, by calling iwconfig.
587 #Parameters:
589 # nothing
591 #Returns:
593 # string or None -- the ESSID or None (if no there is no current association)
594 def get_current_essid( self ):
595 """Returns the current ESSID if any by calling iwconfig"""
596 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
597 # Be careful to the language (inet adr: in French for example)
598 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
599 line = iwconfig_info.read()
600 if essid_re.search( line ):
601 return essid_re.search( line ).group(2)
602 return None
604 # Returns the current BSSID, if any, by calling iwconfig.
606 #Parameters:
608 # nothing
610 #Returns:
612 # string or None -- the BSSID or None (if no there is no current association)
613 def get_current_bssid( self ):
614 """Returns the current BSSID if any by calling iwconfig"""
615 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
616 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
617 line = iwconfig_info.read()
618 if bssid_re.search( line ):
619 return bssid_re.search( line ).group(2)
620 return None
624 # The main user interface window for WiFi Radar. This class also is the control
625 # center for most of the rest of the operations.
626 class radar_window:
627 # Create a new radar_window.
629 #Parameters:
631 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
633 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
635 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
637 #Returns:
639 # radar_window instance
640 def __init__( self, confFile, apQueue, commQueue ):
641 global signal_xpm_none
642 global signal_xpm_low
643 global signal_xpm_barely
644 global signal_xpm_ok
645 global signal_xpm_best
646 global known_profile_icon
647 global unknown_profile_icon
648 global wifi_radar_icon
650 self.confFile = confFile
651 self.apQueue = apQueue
652 self.commandQueue = commQueue
653 self.access_points = {}
654 self.connection = None
656 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
657 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
658 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
659 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
660 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
661 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
662 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
663 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
664 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
665 self.window.set_icon( icon )
666 self.window.set_border_width( 10 )
667 self.window.set_size_request( 550, 300 )
668 self.window.set_title( "WiFi Radar" )
669 self.window.connect( 'delete_event', self.delete_event )
670 self.window.connect( 'destroy', self.destroy )
671 # let's create all our widgets
672 self.current_network = gtk.Label()
673 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
674 self.current_network.show()
675 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
676 self.close_button.show()
677 self.close_button.connect( 'clicked', self.delete_event, None )
678 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
679 self.preferences_button.show()
680 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
681 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
682 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
683 self.plist = gtk.TreeView( self.pstore )
684 # The icons column, known and encryption
685 self.pix_cell = gtk.CellRendererPixbuf()
686 self.wep_cell = gtk.CellRendererPixbuf()
687 self.icons_cell = gtk.CellRendererText()
688 self.icons_col = gtk.TreeViewColumn()
689 self.icons_col.pack_start( self.pix_cell, False )
690 self.icons_col.pack_start( self.wep_cell, False )
691 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
692 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
693 self.plist.append_column( self.icons_col )
694 # The AP column
695 self.ap_cell = gtk.CellRendererText()
696 self.ap_col = gtk.TreeViewColumn( "Access Point" )
697 self.ap_col.pack_start( self.ap_cell, True )
698 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
699 self.plist.append_column( self.ap_col )
700 # The signal column
701 self.sig_cell = gtk.CellRendererPixbuf()
702 self.signal_col = gtk.TreeViewColumn( "Signal" )
703 self.signal_col.pack_start( self.sig_cell, True )
704 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
705 self.plist.append_column( self.signal_col )
706 # The mode column
707 self.mode_cell = gtk.CellRendererText()
708 self.mode_col = gtk.TreeViewColumn( "Mode" )
709 self.mode_col.pack_start( self.mode_cell, True )
710 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
711 self.plist.append_column( self.mode_col )
712 # The protocol column
713 self.prot_cell = gtk.CellRendererText()
714 self.protocol_col = gtk.TreeViewColumn( "802.11" )
715 self.protocol_col.pack_start( self.prot_cell, True )
716 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
717 self.plist.append_column( self.protocol_col )
718 # The channel column
719 self.channel_cell = gtk.CellRendererText()
720 self.channel_col = gtk.TreeViewColumn( "Channel" )
721 self.channel_col.pack_start( self.channel_cell, True )
722 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
723 self.plist.append_column( self.channel_col )
724 # DnD Ordering
725 self.plist.set_reorderable( True )
726 self.pstore.connect( 'row-changed', self.update_auto_profile_order )
727 # enable/disable buttons based on the selected network
728 self.selected_network = self.plist.get_selection()
729 self.selected_network.connect( 'changed', self.on_network_selection, None )
730 # the list scroll bar
731 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
732 sb.show()
733 self.plist.show()
734 # Add New button
735 self.new_button = gtk.Button( "_New" )
736 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
737 self.new_button.show()
738 # Add Configure button
739 self.edit_button = gtk.Button( "C_onfigure" )
740 self.edit_button.connect( 'clicked', self.edit_profile, None )
741 self.edit_button.show()
742 self.edit_button.set_sensitive(False)
743 # Add Delete button
744 self.delete_button = gtk.Button( "_Delete" )
745 self.delete_button.connect( 'clicked', self.delete_profile, None )
746 self.delete_button.show()
747 self.delete_button.set_sensitive(False)
748 # Add Connect button
749 self.connect_button = gtk.Button( "Co_nnect" )
750 self.connect_button.connect( 'clicked', self.connect_profile, None )
751 # Add Disconnect button
752 self.disconnect_button = gtk.Button( "D_isconnect" )
753 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
754 # lets add our widgets
755 rows = gtk.VBox( False, 3 )
756 net_list = gtk.HBox( False, 0 )
757 listcols = gtk.HBox( False, 0 )
758 prows = gtk.VBox( False, 0 )
759 # lets start packing
760 # the network list
761 net_list.pack_start( self.plist, True, True, 0 )
762 net_list.pack_start( sb, False, False, 0 )
763 # the rows level
764 rows.pack_start( net_list , True, True, 0 )
765 rows.pack_start( self.current_network, False, True, 0 )
766 # the list columns
767 listcols.pack_start( rows, True, True, 0 )
768 listcols.pack_start( prows, False, False, 5 )
769 # the list buttons
770 prows.pack_start( self.new_button, False, False, 2 )
771 prows.pack_start( self.edit_button, False, False, 2 )
772 prows.pack_start( self.delete_button, False, False, 2 )
773 prows.pack_end( self.connect_button, False, False, 2 )
774 prows.pack_end( self.disconnect_button, False, False, 2 )
776 self.window.action_area.pack_start( self.preferences_button )
777 self.window.action_area.pack_start( self.close_button )
779 rows.show()
780 prows.show()
781 listcols.show()
782 self.window.vbox.add( listcols )
783 self.window.vbox.set_spacing( 3 )
784 self.window.show_all()
786 # Now, immediately hide these two. The proper one will be
787 # displayed later, based on interface state. -BEF-
788 self.disconnect_button.hide()
789 self.connect_button.hide()
790 self.connect_button.set_sensitive(False)
792 # set up connection manager for later use
793 self.connection = ConnectionManager( self.confFile, self.commandQueue )
795 # Add our known profiles in order
796 for ap in self.confFile.auto_profile_order:
797 ap = ap.strip()
798 self.access_points[ ap ] = self.confFile.get_profile( ap )
799 wep = None
800 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
801 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'] ] )
802 # This is the first run (or, at least, no config file was present), so pop up the preferences window
803 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
804 self.confFile.remove_option('DEFAULT', 'new_file')
805 self.edit_preferences(self.preferences_button)
807 # Begin running radar_window in Gtk event loop.
809 #Parameters:
811 # nothing
813 #Returns:
815 # nothing
816 def main( self ):
817 gtk.main()
819 # Write the config file to disk and quit application.
821 #Parameters:
823 # 'widget' -- gtk.Widget - The widget sending the event.
825 #Returns:
827 # nothing
828 def destroy( self, widget = None):
829 self.confFile.write()
830 gtk.main_quit()
832 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
834 #Parameters:
836 # 'widget' -- gtk.Widget - The widget sending the event.
838 # 'data' -- tuple - list of arbitrary arguments (not used)
840 #Returns:
842 # boolean -- always return False (i.e. do not propigate the signal which called)
843 def delete_event( self, widget, data = None ):
844 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
845 try:
846 self.commandQueue.put("exit", True)
847 except Queue.Full:
848 pass
849 # Save the preferred networks order
850 self.update_auto_profile_order()
851 self.destroy()
852 return False
854 # Updates the on-screen profiles list.
856 #Parameters:
858 # nothing
860 #Returns:
862 # boolean -- always return True
863 def update_plist_items( self ):
864 # Indicate to PyGtk that only one Gtk thread should run here
865 gtk.gdk.threads_enter()
866 # update the current ip and essid
867 # set the state of connect/disconnect buttons based on whether we have an IP address
868 if self.connection:
869 if self.connection.state:
870 self.current_network.set_text( "Connected to %s\nip %s" % ( make_section_name( self.connection.get_current_essid(), self.connection.get_current_bssid() ), self.connection.get_current_ip() ) )
871 self.connect_button.hide()
872 self.disconnect_button.show()
873 else:
874 self.current_network.set_text( "Not Connected." )
875 self.disconnect_button.hide()
876 self.connect_button.show()
878 while True:
879 # Get profiles scanned by iwlist
880 try:
881 profile = self.apQueue.get_nowait()
882 except Queue.Empty:
883 break
884 else:
885 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
886 wep = None
887 if prow_iter != None:
888 # the AP is in the list of APs on the screen
889 apname = make_section_name(profile['essid'], profile['bssid'])
890 if self.access_points.has_key(apname):
891 # This AP has been configured and is/should be stored in the config file
892 profile['known'] = self.access_points[apname]['known']
893 self.access_points[apname]['available'] = profile['available']
894 self.access_points[apname]['encrypted'] = profile['encrypted']
895 self.access_points[apname]['signal'] = profile['signal']
896 self.access_points[apname]['mode'] = profile['mode']
897 self.access_points[apname]['protocol'] = profile['protocol']
898 self.access_points[apname]['channel'] = profile['channel']
899 # Set the 'known' values; False is default, overridden to True by self.access_points
900 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
901 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
902 self.pstore.set_value(prow_iter, 3, profile['available'])
903 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
904 self.pstore.set_value(prow_iter, 4, wep)
905 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
906 self.pstore.set_value(prow_iter, 6, profile['mode'])
907 self.pstore.set_value(prow_iter, 7, profile['protocol'])
908 self.pstore.set_value(prow_iter, 8, profile['channel'])
909 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
910 #for val in self.pstore[prow_iter]:
911 #print val,
912 else:
913 # the AP is not in the list of APs on the screen
914 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'] ] )
915 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
916 # Allow other Gtk threads to run
917 gtk.gdk.threads_leave()
918 #print "update_plist_items: Empty apQueue"
919 return True
921 # Return the proper icon for a value of known.
923 #Parameters:
925 # 'known' -- boolean - Whether the AP is known (i.e. configured)
927 #Returns:
929 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
930 def pixbuf_from_known( self, known ):
931 """ return the proper icon for value of known """
932 if known:
933 return self.known_profile_icon
934 else:
935 return self.unknown_profile_icon
937 # Return an icon indicating the signal level.
939 #Parameters:
941 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
943 #Returns:
945 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
946 def pixbuf_from_signal( self, signal ):
947 signal = int( signal )
948 #print "signal level:", signal
949 if signal < 3:
950 return self.signal_none_pb
951 elif signal < 12:
952 return self.signal_low_pb
953 elif signal < 20:
954 return self.signal_barely_pb
955 elif signal < 35:
956 return self.signal_ok_pb
957 elif signal >= 35:
958 return self.signal_best_pb
959 else:
960 return None
962 # Return row which holds specified ESSID and BSSID.
964 #Parameters:
966 # 'essid' -- string - ESSID to match
968 # 'bssid' -- string - BSSID to match
970 #Returns:
972 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
973 def get_row_by_ap( self, essid, bssid ):
974 for row in self.pstore:
975 if ( row[0] == essid + "\n" + bssid ):
976 #print "matched:", row.iter, essid, bssid
977 return row.iter
978 return None
980 # Enable/disable buttons based on the selected network.
982 #Parameters:
984 # 'widget' -- gtk.Widget - The widget sending the event.
986 # 'data' -- tuple - list of arbitrary arguments (not used)
988 #Returns:
990 # nothing
991 def on_network_selection( self, widget, data = None ):
992 ( store, selected_iter ) = self.selected_network.get_selected()
993 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
994 # if no networks are selected, disable all buttons except New
995 # (this occurs after a drag-and-drop)
996 if selected_iter == None:
997 self.edit_button.set_sensitive(False)
998 self.delete_button.set_sensitive(False)
999 self.connect_button.set_sensitive(False)
1000 return
1001 # enable/disable buttons
1002 self.connect_button.set_sensitive(True)
1003 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1004 self.edit_button.set_sensitive(True)
1005 self.delete_button.set_sensitive(True)
1006 else:
1007 self.edit_button.set_sensitive(True)
1008 self.delete_button.set_sensitive(False)
1010 # Init and run the preferences dialog
1012 #Parameters:
1014 # 'widget' -- gtk.Widget - The widget sending the event.
1016 # 'data' -- tuple - list of arbitrary arguments (not used)
1018 #Returns:
1020 # nothing
1021 def edit_preferences( self, widget, data=None ):
1022 prefs = preferences_dialog( self, self.confFile )
1023 response = prefs.run()
1024 prefs.destroy()
1025 if response == int(gtk.RESPONSE_ACCEPT):
1026 prefs.save()
1028 # Respond to a request to create a new AP profile
1030 #Parameters:
1032 # 'widget' -- gtk.Widget - The widget sending the event.
1034 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1036 # 'data' -- tuple - list of arbitrary arguments (not used)
1038 #Returns:
1040 # boolean -- True if a profile was created and False if profile creation was canceled.
1041 def create_new_profile( self, widget, profile, data=None ):
1042 profile_editor = profile_dialog( self, profile )
1043 profile = profile_editor.run()
1044 profile_editor.destroy()
1045 if profile:
1046 apname = make_section_name( profile['essid'], profile['bssid'] )
1047 # Check that the ap does not exist already
1048 if apname in self.confFile.profiles():
1049 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) )
1050 dlg.run()
1051 dlg.destroy()
1052 del dlg
1053 # try again
1054 self.access_points[ apname ] = profile
1055 self.confFile.set_section( apname, profile )
1056 # if it is not in the auto_profile_order add it
1057 if apname not in self.confFile.auto_profile_order:
1058 self.confFile.auto_profile_order.append(apname)
1059 # add to the store
1060 wep = None
1061 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1062 return True
1063 else:
1064 # Did not create new profile
1065 return False
1067 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1068 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1070 #Parameters:
1072 # 'widget' -- gtk.Widget - The widget sending the event.
1074 # 'data' -- tuple - list of arbitrary arguments (not used)
1076 #Returns:
1078 # nothing
1079 def edit_profile( self, widget, data=None ):
1080 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1081 if not selected_iter: return
1082 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1083 apname = make_section_name( row_start[0], row_start[1] )
1084 profile = self.confFile.get_profile( apname )
1085 if profile:
1086 profile_editor = profile_dialog( self, profile )
1087 profile = profile_editor.run()
1088 profile_editor.destroy()
1089 if profile:
1090 if __debug__:
1091 print "Got edited profile ", profile
1092 apname = make_section_name( profile['essid'], profile['bssid'] )
1093 self.access_points[ apname ] = profile
1094 self.confFile.set_section( apname, profile )
1095 else:
1096 profile = get_new_profile()
1097 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1098 self.create_new_profile( widget, profile, data )
1100 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1102 #Parameters:
1104 # 'widget' -- gtk.Widget - The widget sending the event.
1106 # 'data' -- tuple - list of arbitrary arguments (not used)
1108 #Returns:
1110 # nothing
1111 def delete_profile( self, widget, data=None ):
1112 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1113 if not selected_iter: return
1114 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1115 known = store.get_value( selected_iter, 1 )
1116 if not known: return
1117 dlg = gtk.MessageDialog(
1118 self.window,
1119 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1120 gtk.MESSAGE_QUESTION,
1121 gtk.BUTTONS_YES_NO,
1122 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1123 res = dlg.run()
1124 dlg.destroy()
1125 del dlg
1126 if res == gtk.RESPONSE_NO: return
1127 # Remove it
1128 apname = make_section_name( essid, bssid )
1129 del self.access_points[ apname ]
1130 self.confFile.remove_section( apname )
1131 print "delete_profile: ", apname, ":", self.confFile.auto_profile_order
1132 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1133 self.pstore.remove( selected_iter )
1134 # Let's save our current state
1135 self.update_auto_profile_order()
1137 # Respond to a request to connect to an AP.
1139 #Parameters:
1141 # 'widget' -- gtk.Widget - The widget sending the event.
1143 # 'profile' -- dictionary - The AP profile to which to connect.
1145 # 'data' -- tuple - list of arbitrary arguments (not used)
1147 #Returns:
1149 # nothing
1150 def connect_profile( self, widget, profile, data=None ):
1151 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1152 if not selected_iter: return
1153 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1154 known = store.get_value( selected_iter, 2 )
1155 if not known:
1156 if data != 'noconnect':
1157 dlg = gtk.MessageDialog(
1158 self.window,
1159 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1160 gtk.MESSAGE_QUESTION,
1161 gtk.BUTTONS_YES_NO,
1162 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1163 res = dlg.run()
1164 dlg.destroy()
1165 del dlg
1166 if res == gtk.RESPONSE_NO: return
1167 profile = get_new_profile()
1168 profile['essid'] = essid
1169 profile['bssid'] = bssid
1170 if not self.create_new_profile( widget, profile, data ):
1171 return
1172 apname = make_section_name( essid, bssid )
1173 self.connection.connect_to_network( self.access_points[apname], status_window( self ) )
1175 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1177 #Parameters:
1179 # 'widget' -- gtk.Widget - The widget sending the event.
1181 # 'data' -- tuple - list of arbitrary arguments (not used)
1183 #Returns:
1185 # nothing
1186 def disconnect_profile( self, widget, data=None ):
1187 self.connection.disconnect_interface()
1189 # Update the config file auto profile order from the on-screen order
1191 #Parameters:
1193 # 'widget' -- gtk.Widget - The widget sending the event.
1195 # 'data' -- tuple - list of arbitrary arguments (not used)
1197 # 'data2' -- tuple - list of arbitrary arguments (not used)
1199 #Returns:
1201 # nothing
1202 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1203 # recreate the auto_profile_order
1204 auto_profile_order = []
1205 piter = self.pstore.get_iter_first()
1206 while piter:
1207 # only if it's known
1208 if self.pstore.get_value( piter, 2 ) == True:
1209 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1210 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1211 piter = self.pstore.iter_next( piter )
1212 self.confFile.auto_profile_order = auto_profile_order
1214 # The preferences dialog. Edits some items in the DEFAULT section of the config file.
1215 class preferences_dialog:
1216 # Create a new preferences_dialog.
1218 #Parameters:
1220 # 'parent' -- gtk.Object - Usually, the calling window.
1222 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1224 #Returns:
1226 # preferences_dialog instance
1227 def __init__( self, parent, confFile ):
1228 global wifi_radar_icon
1229 self.parent = parent
1230 self.confFile = confFile
1231 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1232 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1233 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1234 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1235 self.dialog.set_icon( icon )
1236 self.dialog.set_resizable( True )
1237 self.dialog.set_transient_for( self.parent.window )
1238 self.tooltips = gtk.Tooltips()
1240 # set up preferences widgets
1243 # auto detect wireless device
1244 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1246 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1248 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1249 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1250 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1251 self.dialog.vbox.pack_start(self.w_auto_detect, False, False, 5)
1252 # network interface selecter
1253 self.w_interface = gtk.combo_box_entry_new_text()
1254 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1255 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1256 for device in wireless_devices:
1257 if device != self.confFile.get_opt('DEFAULT.interface'):
1258 self.w_interface.append_text(device)
1259 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1260 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1261 self.w_interface.set_active(0)
1262 self.w_interface_label = gtk.Label("Wireless device")
1263 self.w_hbox1 = gtk.HBox(False, 0)
1264 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1265 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1266 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1267 self.dialog.vbox.pack_start(self.w_hbox1, False, False, 5)
1268 # scan timeout (spin button of integers from 1 to 100)
1269 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,1 )
1270 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1271 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1272 self.w_scan_timeout.set_numeric(True)
1273 self.w_scan_timeout.set_snap_to_ticks(True)
1274 self.w_scan_timeout.set_wrap(False)
1275 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1276 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1277 self.w_hbox2 = gtk.HBox(False, 0)
1278 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1279 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1280 self.dialog.vbox.pack_start(self.w_hbox2, False, False, 5)
1281 # speak up
1282 self.w_speak_up = gtk.CheckButton("Use speak-up")
1283 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1284 self.w_speak_up.connect("toggled", self.toggle_speak)
1285 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1286 self.dialog.vbox.pack_start(self.w_speak_up, False, False, 5)
1288 # speak up command
1289 self.w_speak_cmd = gtk.Entry()
1290 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1291 self.w_speak_cmd_label = gtk.Label("Speak Command")
1292 self.w_hbox3 = gtk.HBox(False, 0)
1293 self.w_hbox3.pack_start(self.w_speak_cmd_label, False, False, 5)
1294 self.w_hbox3.pack_start(self.w_speak_cmd, True, True, 0)
1295 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1296 self.dialog.vbox.pack_start(self.w_hbox3, False, False, 5)
1298 # commit required
1299 self.w_commit_required = gtk.CheckButton("Commit required")
1300 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1301 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1302 self.dialog.vbox.pack_start(self.w_commit_required, False, False, 5)
1303 # ifup required
1304 self.w_ifup_required = gtk.CheckButton("Ifup required")
1305 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1306 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1307 # ifconfig command
1308 self.w_ifconfig_cmd = gtk.Entry()
1309 self.w_ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1310 self.w_ifconfig_cmd_label = gtk.Label("ifconfig Command")
1311 self.w_hbox4 = gtk.HBox(True, 0)
1312 self.w_hbox4.pack_start(self.w_ifconfig_cmd_label, False, False, 5)
1313 self.w_hbox4.pack_start(self.w_ifconfig_cmd, True, True, 0)
1314 self.dialog.vbox.pack_start(self.w_hbox4, False, False, 5)
1315 self.dialog.vbox.pack_start(self.w_ifup_required, False, False, 5)
1317 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1319 #Parameters:
1321 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1323 # 'data' -- tuple - list of arbitrary arguments (not used)
1325 #Returns:
1327 # nothing
1328 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1329 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1331 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1333 #Parameters:
1335 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1337 # 'data' -- tuple - list of arbitrary arguments (not used)
1339 #Returns:
1341 # nothing
1342 def toggle_speak(self, speak_toggle, data=None):
1343 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1345 # Display preferences dialog and operate until canceled or okayed.
1347 #Parameters:
1349 # nothing
1351 #Returns:
1353 # integer -- gtk response ID
1354 def run(self):
1355 self.dialog.show_all()
1356 return self.dialog.run()
1358 # Write updated values to config file.
1360 #Parameters:
1362 # nothing
1364 #Returns:
1366 # nothing
1367 def save(self):
1368 print "saving prefs"
1369 if self.w_auto_detect.get_active():
1370 set_network_device("auto_detect")
1371 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1372 else:
1373 set_network_device(self.w_interface.child.get_text())
1374 self.confFile.set_opt('DEFAULT.interface', self.w_interface.child.get_text())
1375 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1376 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1377 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1378 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1379 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
1380 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'up'])
1382 # Remove preferences window.
1384 #Parameters:
1386 # nothing
1388 #Returns:
1390 # nothing
1391 def destroy(self):
1392 self.dialog.destroy()
1393 del self.dialog
1396 # Edit and return an AP profile.
1397 class profile_dialog:
1398 # Create a new profile_dialog.
1400 #Parameters:
1402 # 'parent' -- gtk.Object - Usually, the calling window.
1404 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1406 #Returns:
1408 # profile_dialog instance
1409 def __init__( self, parent, profile ):
1410 global wifi_radar_icon
1411 self.parent = parent
1412 self.profile = profile
1413 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1414 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1415 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1416 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1417 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1418 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1419 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1420 self.dialog.set_icon( icon )
1421 self.dialog.set_resizable( False )
1422 self.dialog.set_transient_for( self.parent.window )
1423 #self.dialog.set_size_request( 400, 400 )
1424 #################
1425 essid_table = gtk.Table( 1, 2, False )
1426 essid_table.set_row_spacings( 3 )
1427 essid_table.set_col_spacings( 3 )
1429 # The essid labels
1430 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1431 # The essid textboxes
1432 self.essid_entry = gtk.Entry( 32 )
1433 self.essid_entry.set_text( self.profile['essid'] )
1434 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1435 # Add the essid table to the dialog
1436 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1438 bssid_table = gtk.Table( 1, 2, False )
1439 bssid_table.set_row_spacings( 3 )
1440 bssid_table.set_col_spacings( 3 )
1441 # The bssid labels
1442 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1443 # The bssid textboxes
1444 self.bssid_entry = gtk.Entry( 32 )
1445 self.bssid_entry.set_text( self.profile['bssid'] )
1446 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1447 #self.key = gtk.Entry( 32 )
1448 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1449 # Add the bssid table to the dialog
1450 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1452 # create the wifi expander
1453 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1454 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1455 wifi_table = gtk.Table( 4, 2, False )
1456 wifi_table.set_row_spacings( 3 )
1457 wifi_table.set_col_spacings( 3 )
1458 # The Wifi labels
1459 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1460 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1461 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1462 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1463 # The Wifi text boxes
1464 self.mode_combo = gtk.combo_box_new_text()
1465 for mode in self.WIFI_MODES:
1466 self.mode_combo.append_text( mode )
1467 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1468 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1469 self.channel_combo = gtk.combo_box_new_text()
1470 for channel in self.WIFI_CHANNELS:
1471 self.channel_combo.append_text( channel )
1472 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1473 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1475 self.key_entry = gtk.Entry( 64 )
1476 self.key_entry.set_text( self.profile['key'] )
1477 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1479 self.security_combo = gtk.combo_box_new_text()
1480 for security in self.WIFI_SECURITY:
1481 self.security_combo.append_text( security )
1482 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1483 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1484 # Add the wifi table to the expander
1485 self.wifi_expander.add( wifi_table )
1486 # Add the expander to the dialog
1487 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1489 # create the wpa expander
1490 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1491 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1492 wpa_table = gtk.Table( 1, 2, False )
1493 wpa_table.set_row_spacings( 3 )
1494 wpa_table.set_col_spacings( 3 )
1495 # The labels
1496 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1497 # The text boxes
1498 self.wpa_driver_entry = gtk.Entry()
1499 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1500 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1501 # Add the wpa table to the expander
1502 self.wpa_expander.add( wpa_table )
1503 # Add the expander to the dialog
1504 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1506 # create the dhcp expander
1507 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1508 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1509 ip_table = gtk.Table( 6, 2, False )
1510 ip_table.set_row_spacings( 3 )
1511 ip_table.set_col_spacings( 3 )
1512 # The IP labels
1513 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1514 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1515 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1516 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1517 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1518 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1519 # The IP text boxes
1520 self.ip_entry = gtk.Entry( 15 )
1521 self.ip_entry.set_text( self.profile['ip'] )
1522 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1523 self.netmask_entry = gtk.Entry( 15 )
1524 self.netmask_entry.set_text( self.profile['netmask'] )
1525 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1526 self.gw_entry = gtk.Entry( 15 )
1527 self.gw_entry.set_text( self.profile['gateway'] )
1528 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1529 self.domain_entry = gtk.Entry( 32 )
1530 self.domain_entry.set_text( self.profile['domain'] )
1531 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1532 self.dns1_entry = gtk.Entry( 15 )
1533 self.dns1_entry.set_text( self.profile['dns1'] )
1534 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1535 self.dns2_entry = gtk.Entry( 15 )
1536 self.dns2_entry.set_text( self.profile['dns2'] )
1537 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1538 # Add the ip table to the expander
1539 self.dhcp_expander.add( ip_table )
1540 # Add the expander to the dialog
1541 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1543 # create the connection-building postpre expander
1544 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1545 con_pp_table = gtk.Table( 2, 2, False )
1546 con_pp_table.set_row_spacings( 3 )
1547 con_pp_table.set_col_spacings( 3 )
1548 # The labels
1549 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1550 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1551 # The text boxes
1552 self.con_prescript_entry = gtk.Entry()
1553 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1554 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1555 self.con_postscript_entry = gtk.Entry()
1556 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1557 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1558 # Add the pp table to the expander
1559 self.con_pp_expander.add( con_pp_table )
1560 # Add the expander to the dialog
1561 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1563 # create the disconnection postpre expander
1564 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1565 dis_pp_table = gtk.Table( 2, 2, False )
1566 dis_pp_table.set_row_spacings( 3 )
1567 dis_pp_table.set_col_spacings( 3 )
1568 # The labels
1569 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1570 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1571 # The text boxes
1572 self.dis_prescript_entry = gtk.Entry()
1573 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1574 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1575 self.dis_postscript_entry = gtk.Entry()
1576 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1577 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1578 # Add the pp table to the expander
1579 self.dis_pp_expander.add( dis_pp_table )
1580 # Add the expander to the dialog
1581 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1583 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1585 #Parameters:
1587 # nothing
1589 #Returns:
1591 # dictionary or None -- a profile, or None on cancel
1592 def run( self ):
1593 self.dialog.show_all()
1594 if self.dialog.run():
1595 self.profile['known'] = True
1596 self.profile['essid'] = self.essid_entry.get_text().strip()
1597 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1598 self.profile['key'] = self.key_entry.get_text().strip()
1599 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1600 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1601 self.profile['encrypted'] = ( self.profile['security'] != '' )
1602 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1603 self.profile['protocol'] = 'g'
1604 self.profile['available'] = ( self.profile['signal'] > 0 )
1605 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1606 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1607 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1608 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1609 # wpa
1610 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1611 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1612 # dhcp
1613 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1614 self.profile['ip'] = self.ip_entry.get_text().strip()
1615 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1616 self.profile['gateway'] = self.gw_entry.get_text().strip()
1617 self.profile['domain'] = self.domain_entry.get_text().strip()
1618 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1619 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1620 return self.profile
1621 return None
1623 # Remove profile dialog.
1625 #Parameters:
1627 # nothing
1629 #Returns:
1631 # nothing
1632 def destroy( self ):
1633 self.dialog.destroy()
1634 del self.dialog
1636 # Respond to expanding/hiding IP segment.
1638 #Parameters:
1640 # 'widget' -- gtk.Widget - The widget sending the event.
1642 # 'data' -- tuple - List of arbitrary arguments (not used)
1644 #Returns:
1646 # nothing
1647 def toggle_use_dhcp( self, widget, data = None ):
1648 expanded = self.dhcp_expander.get_expanded()
1649 if expanded:
1650 self.dhcp_expander.set_label( USE_IP_LABEL )
1651 else:
1652 self.dhcp_expander.set_label( USE_DHCP_LABEL )
1654 # Respond to expanding/hiding WPA segment.
1656 #Parameters:
1658 # 'widget' -- gtk.Widget - The widget sending the event.
1660 # 'data' -- tuple - List of arbitrary arguments (not used)
1662 #Returns:
1664 # nothing
1665 def toggle_use_wpa( self, widget, data = None ):
1666 expanded = self.wpa_expander.get_expanded()
1667 if expanded:
1668 self.wpa_expander.set_label( USE_WPA_LABEL )
1669 else:
1670 self.wpa_expander.set_label( NO_WPA_LABEL )
1672 # Return the index where item matches a cell in array.
1674 #Parameters:
1676 # 'item' -- string - Item to find in array
1678 # 'array' -- list - List in which to find match.
1680 #Returns:
1682 # integer - 0 (no match) or higher (index of match)
1683 def get_array_index( self, item, array ):
1684 try:
1685 return array.index( item.strip() )
1686 except:
1687 pass
1688 return 0
1690 # Return the value in array[ index ]
1692 #Parameters:
1694 # 'index' -- integer - The index to look up.
1696 # 'array' -- list - List in which to look up value.
1698 #Returns:
1700 # string -- empty string (no match) or looked up value
1701 def get_array_item( self, index, array ):
1702 try:
1703 return array[ index ]
1704 except:
1705 pass
1706 return ''
1708 # A simple class for putting up a "Please wait" dialog so the user
1709 # doesn't think we've forgotten about them.
1710 class status_window:
1711 # Create a new status_window.
1713 #Parameters:
1715 # 'parent' -- gtk.Object - Usually, the calling window.
1717 #Returns:
1719 # status_window instance
1720 def __init__( self, parent ):
1721 global wifi_radar_icon
1722 self.parent = parent
1723 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
1724 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1725 self.dialog.set_icon( icon )
1726 self.lbl = gtk.Label("Please wait...")
1727 self.bar = gtk.ProgressBar()
1728 self.dialog.vbox.pack_start(self.lbl)
1729 self.dialog.vbox.pack_start(self.bar)
1730 self.dialog.show_all()
1732 # Change the message displayed to the user.
1734 #Parameters:
1736 # 'message' -- string - The message to show to the user.
1738 #Returns:
1740 # nothing
1741 def update_message( self, message ):
1742 self.lbl.set_text(message)
1744 # Update the status_window progress bar.
1746 #Parameters:
1748 # nothing
1750 #Returns:
1752 # True -- always return True
1753 def update_window( self ):
1754 self.bar.pulse()
1755 return True
1757 # Display and operate the status_window.
1759 #Parameters:
1761 # nothing
1763 #Returns:
1765 # nothing
1766 def run( self ):
1767 self.dialog.show_all()
1768 self.timer = gobject.timeout_add(250,self.update_window)
1769 return
1771 # Remove the status_window.
1773 #Parameters:
1775 # nothing
1777 #Returns:
1779 # nothing
1780 def destroy( self ):
1781 gobject.source_remove(self.timer)
1782 self.dialog.destroy()
1783 del self.dialog
1785 # Manage the configuration for the application, including reading and writing the config from/to a file.
1786 class ConfigFile(ConfigParser.SafeConfigParser):
1787 # Create a new ConfigFile.
1789 #Parameters:
1791 # 'filename' -- string - The configuration file's name.
1793 # 'defaults' -- dictionary - Default values for the DEFAULT section.
1795 #Returns:
1797 # ConfigFile instance
1798 def __init__( self, filename, defaults ):
1799 self.filename = filename
1800 self.auto_profile_order = []
1801 ConfigParser.SafeConfigParser.__init__(self, defaults)
1803 # Set the contents of a section to values from a dictionary.
1805 #Parameters:
1807 # 'section_name' -- string - Configuration file section.
1809 # 'section_dict' -- dictionary - Values to add to section.
1811 #Returns:
1813 # nothing
1814 def set_section( self, section_name, section_dict ):
1815 try:
1816 self.add_section(section_name)
1817 except ConfigParser.DuplicateSectionError:
1818 pass
1819 for key in section_dict.keys():
1820 if type(section_dict[key]) == BooleanType:
1821 self.set_bool_opt(section_name + "." + key, section_dict[key])
1822 elif type(section_dict[key]) == IntType:
1823 self.set_int_opt(section_name + "." + key, section_dict[key])
1824 elif type(section_dict[key]) == FloatType:
1825 self.set_float_opt(section_name + "." + key, section_dict[key])
1826 else:
1827 self.set_opt(section_name + "." + key, section_dict[key])
1829 # Return the profile recorded in the specified section.
1831 #Parameters:
1833 # 'section_name' -- string - Configuration file section.
1835 #Returns:
1837 # dictionary or None - The specified profile or None if not found
1838 def get_profile( self, section_name ):
1839 if section_name in self.profiles():
1840 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' ]
1841 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
1842 int_types = [ 'signal' ]
1843 profile = {}
1844 for option in bool_types:
1845 profile[option] = self.get_opt_as_bool( section_name + "." + option )
1846 for option in int_types:
1847 profile[option] = self.get_opt_as_int( section_name + "." + option )
1848 for option in str_types:
1849 profile[option] = self.get_opt( section_name + "." + option )
1850 return profile
1851 return None
1853 # Get a config option and handle exceptions.
1855 #Parameters:
1857 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1858 # period and the option key. (E.g. "DEFAULT.interface")
1860 #Returns:
1862 # string or None - option value as string or None on failure
1863 def get_opt( self, option_path ):
1864 #print "ConfigFile.get_opt: ", option_path
1865 (section, option) = option_path.split('.')
1866 try:
1867 return self.get(section, option)
1868 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
1869 return None
1871 # Get a config option and return as a boolean type.
1873 #Parameters:
1875 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1876 # period and the option key. (E.g. "DEFAULT.interface")
1878 #Returns:
1880 # boolean - option value as boolean
1881 def get_opt_as_bool( self, option_path ):
1882 option = self.get_opt(option_path)
1883 if isinstance(option, BooleanType) or isinstance(option, NoneType):
1884 return option
1885 if option == 'True':
1886 return True
1887 if option == 'False':
1888 return False
1889 raise ValueError, 'boolean option was not True or False'
1891 # Get a config option and return as an integer type.
1893 #Parameters:
1895 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1896 # period and the option key. (E.g. "DEFAULT.interface")
1898 #Returns:
1900 # integer- option value as integer
1901 def get_opt_as_int( self, option_path ):
1902 return int(float(self.get_opt(option_path)))
1904 # Convert boolean type to string and set config option.
1906 #Parameters:
1908 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1909 # period and the option key. (E.g. "DEFAULT.interface")
1911 # 'value' -- boolean - Value to set.
1913 #Returns:
1915 # nothing
1916 def set_bool_opt( self, option_path, value ):
1917 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
1918 value == 'True'
1919 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
1920 value == 'False'
1921 else:
1922 raise ValueError, 'cannot convert value to string'
1923 self.set_opt(option_path, repr(value))
1925 # Convert integer type to string and set config option.
1927 #Parameters:
1929 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1930 # period and the option key. (E.g. "DEFAULT.interface")
1932 # 'value' -- integer - Value to set.
1934 #Returns:
1936 # nothing
1937 def set_int_opt( self, option_path, value ):
1938 if not isinstance(value, IntType):
1939 raise ValueError, 'value is not an integer'
1940 self.set_opt(option_path, repr(value))
1942 # Convert float type to string and set config option.
1944 #Parameters:
1946 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1947 # period and the option key. (E.g. "DEFAULT.interface")
1949 # 'value' -- float - Value to set.
1951 #Returns:
1953 # nothing
1954 def set_float_opt( self, option_path, value ):
1955 if not isinstance(value, FloatType):
1956 raise ValueError, 'value is not a float'
1957 self.set_opt(option_path, repr(int(value)))
1959 # Set a config option while handling exceptions.
1961 #Parameters:
1963 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
1964 # period and the option key. (E.g. "DEFAULT.interface")
1966 # 'value' -- string - Value to set.
1968 #Returns:
1970 # nothing
1971 def set_opt( self, option_path, value ):
1972 (section, option) = option_path.split('.')
1973 try:
1974 self.set(section, option, value)
1975 except ConfigParser.NoSectionError:
1976 self.add_section(section)
1977 self.set_opt(option_path, value)
1979 # Return a list of the section names which denote AP profiles.
1981 #Parameters:
1983 # nothing
1985 #Returns:
1987 # list - profile names
1988 def profiles( self ):
1989 profile_list = []
1990 for section in self.sections():
1991 if ':' in section:
1992 profile_list.append(section)
1993 return profile_list
1995 # Read configuration file from disk into instance variables.
1997 #Parameters:
1999 # nothing
2001 #Returns:
2003 # nothing
2004 def read( self ):
2005 fp = open( self.filename, "r" )
2006 self.readfp(fp)
2007 # convert the auto_profile_order to a list for ordering
2008 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2009 for ap in self.profiles():
2010 self.set_bool_opt( ap + '.known', True)
2011 if ap in self.auto_profile_order: continue
2012 self.auto_profile_order.append( ap )
2014 # Write configuration file to disk from instance variables. Copied from
2015 # ConfigParser and modified to write options in alphabetical order.
2017 #Parameters:
2019 # nothing
2021 #Returns:
2023 # nothing
2024 def write( self ):
2025 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2026 fp = open( self.filename, "w" )
2027 # write DEFAULT section first
2028 if self._defaults:
2029 fp.write("[DEFAULT]\n")
2030 for key in sorted(self._defaults.keys()):
2031 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2032 fp.write("\n")
2033 # write non-profile sections first
2034 for section in self._sections:
2035 if section not in self.profiles():
2036 fp.write("[%s]\n" % section)
2037 for key in sorted(self._sections[section].keys()):
2038 if key != "__name__":
2039 fp.write("%s = %s\n" %
2040 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2041 fp.write("\n")
2042 # write profile sections
2043 for section in self._sections:
2044 if section in self.profiles():
2045 fp.write("[%s]\n" % section)
2046 for key in sorted(self._sections[section].keys()):
2047 if key != "__name__":
2048 fp.write("%s = %s\n" %
2049 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2050 fp.write("\n")
2054 # Load our conf file and known profiles
2055 # Defaults, these may get overridden by values found in the conf file.
2056 config_defaults = { # The network interface you use.
2057 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2058 'interface': "auto_detect",
2059 # How long should the scan for access points last?
2060 'scan_timeout': '5',
2061 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2062 # Set the speak_up option to false if you do not have or want this.
2063 'speak_command': '/usr/bin/say',
2064 # Should I speak up when connecting to a network? (If you have a speech command)
2065 'speak_up': 'False',
2066 # You may set this to true for cards that require a "commit" command with iwconfig
2067 'commit_required': 'False',
2068 # You may set this to true for cards that require the interface to be brought up first
2069 'ifup_required': 'False',
2070 # Set the location of several important programs
2071 'iwlist_command': '/sbin/iwlist',
2072 'iwconfig_command': '/sbin/iwconfig',
2073 'ifconfig_command': '/sbin/ifconfig',
2074 'route_command': '/sbin/route',
2075 'auto_profile_order': '[]',
2076 'version': WIFI_RADAR_VERSION }
2078 config_dhcp = { # DHCP client
2079 'command': 'dhcpcd',
2080 # How long to wait for an IP addr from DHCP server
2081 'timeout': '30',
2082 # Arguments to use with DHCP client on connect
2083 'args': '-D -o -i dhcp_client -t %(timeout)s',
2084 # Argument to use with DHCP client on disconnect
2085 'kill_args': '-k',
2086 # The file where DHCP client PID is written
2087 'pidfile': '/etc/dhcpcd-%(interface)s.pid' }
2089 config_wpa = { # WPA Supplicant
2090 'command': '/usr/sbin/wpa_supplicant',
2091 # Arguments to use with WPA Supplicant on connect
2092 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2093 # Arguments to use with WPA Supplicant on disconnect
2094 'kill_command': '',
2095 # Where the WPA Supplicant config file can be found
2096 'configuration': '/etc/wpa_supplicant.conf',
2097 # Driver to use with WPA Supplicant
2098 'driver': 'wext',
2099 # The file where WPA Supplicant PID is written
2100 'pidfile': '/var/run/wpa_supplicant.pid' }
2102 # initialize config, with defaults
2103 confFile = ConfigFile(CONF_FILE, config_defaults)
2104 confFile.set_section("DHCP", config_dhcp)
2105 confFile.set_section("WPA", config_wpa)
2107 if not os.path.isfile( CONF_FILE ):
2108 confFile.set_bool_opt('DEFAULT.new_file', True)
2109 else:
2110 if not os.access(CONF_FILE, os.R_OK):
2111 print "Can't open " + CONF_FILE + "."
2112 print "Are you root?"
2113 sys.exit()
2114 confFile.read()
2117 ####################################################################################################
2118 # Embedded Images
2119 wifi_radar_icon = [ ""
2120 "GdkP"
2121 "\0\0\22""7"
2122 "\2\1\0\2"
2123 "\0\0\1\214"
2124 "\0\0\0c"
2125 "\0\0\0O"
2126 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2127 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2128 "\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"
2129 "\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"
2130 "\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"
2131 "\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"
2132 "\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"
2133 "\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"
2134 "\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"
2135 "\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"
2136 "\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"
2137 "\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"
2138 "\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"
2139 "\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"
2140 "\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"
2141 "\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"
2142 "\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"
2143 "\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"
2144 "\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"
2145 "\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"
2146 "\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"
2147 "\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"
2148 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2149 "\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"
2150 "\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"
2151 "\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"
2152 "\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"
2153 "\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"
2154 "\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"
2155 "\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"
2156 "\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"
2157 "\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"
2158 "\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"
2159 "\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"
2160 "\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"
2161 "\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"
2162 "\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"
2163 "\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"
2164 "\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"
2165 "\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"
2166 "\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"
2167 "\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"
2168 "\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"
2169 "\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"
2170 "\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"
2171 "\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"
2172 "\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"
2173 "\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"
2174 "\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"
2175 "\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"
2176 "\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"
2177 "\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"
2178 "\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"
2179 "\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"
2180 "\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"
2181 "\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"
2182 "\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"
2183 "\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"
2184 "\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"
2185 "\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"
2186 "\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"
2187 "\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"
2188 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2189 "\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"
2190 "\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"
2191 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2192 "\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"
2193 "\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"
2194 "\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"
2195 "\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"
2196 "\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"
2197 "\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"
2198 "\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"
2199 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2200 "\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"
2201 "\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"
2202 "\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"
2203 "\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"
2204 "\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"
2205 "\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"
2206 "|\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"
2207 "\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"
2208 "\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"
2209 "\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"
2210 "\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"
2211 "\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"
2212 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2213 "\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"
2214 "\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"
2215 "\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"
2216 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2217 "\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"
2218 "\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"
2219 "\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"
2220 "\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"
2221 "\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"
2222 "\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"
2223 "\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"
2224 "\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"
2225 "\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"
2226 "\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"
2227 "\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"
2228 "\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"
2229 "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"
2230 "\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"
2231 "\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"
2232 "\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"
2233 "\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"
2234 "\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"
2235 "\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"
2236 "\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"
2237 "\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"
2238 "\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"
2239 "\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"
2240 "\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"
2241 "\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"
2242 "\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"
2243 "\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"
2244 "\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|"
2245 "\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"
2246 "\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"
2247 "\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"
2248 "\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"
2249 "\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"
2250 "\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"
2251 "\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"
2252 "\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"
2253 "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"
2254 "\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"
2255 "\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"
2256 "\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"
2257 "\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"
2258 "\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"
2259 "\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"
2260 "\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"
2261 "\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"
2262 "\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"
2263 "\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"
2264 "\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"
2265 "\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"
2266 "\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"
2267 "\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"
2268 "\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"
2269 "\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"
2270 "\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"
2271 "\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"
2272 "\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"
2273 "\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"
2274 "\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"
2275 "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"
2276 "\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"
2277 "\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"
2278 "\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"
2279 "\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"
2280 "\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"
2281 "\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"
2282 "\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"
2283 "\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"
2284 "\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"
2285 "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"
2286 "\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"
2287 "\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"
2288 "\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"
2289 "\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"
2290 "\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"
2291 "\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"
2292 "\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"
2293 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2294 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2295 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2296 "\0"]
2298 known_profile_icon = [ ""
2299 "GdkP"
2300 "\0\0\5""0"
2301 "\2\1\0\2"
2302 "\0\0\0P"
2303 "\0\0\0\24"
2304 "\0\0\0\24"
2305 "\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"
2306 "\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"
2307 "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"
2308 "\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"
2309 "\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"
2310 "\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"
2311 "\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"
2312 "\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"
2313 "\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"
2314 "\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"
2315 "\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"
2316 "\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"
2317 "\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"
2318 "\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"
2319 "\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"
2320 "\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"
2321 "\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"
2322 "\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"
2323 "\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"
2324 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2325 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2326 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2327 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2328 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2329 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2330 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2331 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2332 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2333 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2334 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2335 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2336 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2337 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2338 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2339 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2340 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2341 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2342 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2343 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2344 "\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"
2345 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2346 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2347 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2348 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2349 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2350 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2351 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2352 "\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"
2353 "\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"
2354 "\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"
2355 "\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"
2356 "\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"]
2358 unknown_profile_icon = [ ""
2359 "GdkP"
2360 "\0\0\5\22"
2361 "\2\1\0\2"
2362 "\0\0\0P"
2363 "\0\0\0\24"
2364 "\0\0\0\24"
2365 "\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"
2366 "\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"
2367 "\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"
2368 "\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"
2369 "(\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"
2370 "\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"
2371 "#\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"
2372 "\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"
2373 "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"
2374 "\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"
2375 "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"
2376 "\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"
2377 "\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"
2378 "\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"
2379 "\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"
2380 "\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"
2381 "\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"
2382 "\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"
2383 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2384 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2385 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2386 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2387 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2388 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2389 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2390 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2391 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2392 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2393 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2394 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2395 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2396 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2397 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2398 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2399 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2400 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2401 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2402 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2403 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2404 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2405 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2406 "\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"
2407 "\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"
2408 "\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"
2409 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2411 signal_xpm_barely = [
2412 "20 20 10 1",
2413 " c None",
2414 ". c #C6C6C6",
2415 "+ c #CCCCCC",
2416 "@ c #DBDBDB",
2417 "# c #D3D3D3",
2418 "$ c #A9B099",
2419 "% c #95A173",
2420 "& c #6B8428",
2421 "* c #B4B7AC",
2422 "= c #80924D",
2423 " .+++.",
2424 " +@@@+",
2425 " +@@@+",
2426 " +@@@+",
2427 " +@@@+",
2428 " .++++#@@@+",
2429 " +@@@@@@@@+",
2430 " +@@@@@@@@+",
2431 " +@@@@@@@@+",
2432 " +@@@@@@@@+",
2433 " $%%%%#@@@@@@@@+",
2434 " %&&&&@@@@@@@@@+",
2435 " %&&&&@@@@@@@@@+",
2436 " %&&&&@@@@@@@@@+",
2437 " %&&&&@@@@@@@@@+",
2438 "*%%%%=&&&&@@@@@@@@@+",
2439 "%&&&&&&&&&@@@@@@@@@+",
2440 "%&&&&&&&&&@@@@@@@@@+",
2441 "%&&&&&&&&&@@@@@@@@@+",
2442 "*%%%%%%%%%+++++++++."
2446 signal_xpm_best = [
2447 "20 20 6 1",
2448 " c None",
2449 ". c #9DAABF",
2450 "+ c #7B96BF",
2451 "@ c #386EBF",
2452 "# c #5982BF",
2453 "$ c #AEB4BF",
2454 " .+++.",
2455 " +@@@+",
2456 " +@@@+",
2457 " +@@@+",
2458 " +@@@+",
2459 " .++++#@@@+",
2460 " +@@@@@@@@+",
2461 " +@@@@@@@@+",
2462 " +@@@@@@@@+",
2463 " +@@@@@@@@+",
2464 " .++++#@@@@@@@@+",
2465 " +@@@@@@@@@@@@@+",
2466 " +@@@@@@@@@@@@@+",
2467 " +@@@@@@@@@@@@@+",
2468 " +@@@@@@@@@@@@@+",
2469 "$++++#@@@@@@@@@@@@@+",
2470 "+@@@@@@@@@@@@@@@@@@+",
2471 "+@@@@@@@@@@@@@@@@@@+",
2472 "+@@@@@@@@@@@@@@@@@@+",
2473 "$++++++++++++++++++."
2476 signal_xpm_none = [
2477 "20 20 6 1",
2478 " c None",
2479 ". c #C6C6C6",
2480 "+ c #CCCCCC",
2481 "@ c #DBDBDB",
2482 "# c #D3D3D3",
2483 "$ c #C2C2C2",
2484 " .+++.",
2485 " +@@@+",
2486 " +@@@+",
2487 " +@@@+",
2488 " +@@@+",
2489 " .++++#@@@+",
2490 " +@@@@@@@@+",
2491 " +@@@@@@@@+",
2492 " +@@@@@@@@+",
2493 " +@@@@@@@@+",
2494 " .++++#@@@@@@@@+",
2495 " +@@@@@@@@@@@@@+",
2496 " +@@@@@@@@@@@@@+",
2497 " +@@@@@@@@@@@@@+",
2498 " +@@@@@@@@@@@@@+",
2499 "$++++#@@@@@@@@@@@@@+",
2500 "+@@@@@@@@@@@@@@@@@@+",
2501 "+@@@@@@@@@@@@@@@@@@+",
2502 "+@@@@@@@@@@@@@@@@@@+",
2503 "$++++++++++++++++++."
2506 signal_xpm_ok = [
2507 "20 20 10 1",
2508 " c None",
2509 ". c #C6C6C6",
2510 "+ c #CCCCCC",
2511 "@ c #DBDBDB",
2512 "# c #A1A5B2",
2513 "$ c #848DA5",
2514 "% c #D3D3D3",
2515 "& c #4A5B8C",
2516 "* c #677498",
2517 "= c #B0B2B8",
2518 " .+++.",
2519 " +@@@+",
2520 " +@@@+",
2521 " +@@@+",
2522 " +@@@+",
2523 " #$$$$%@@@+",
2524 " $&&&&@@@@+",
2525 " $&&&&@@@@+",
2526 " $&&&&@@@@+",
2527 " $&&&&@@@@+",
2528 " #$$$$*&&&&@@@@+",
2529 " $&&&&&&&&&@@@@+",
2530 " $&&&&&&&&&@@@@+",
2531 " $&&&&&&&&&@@@@+",
2532 " $&&&&&&&&&@@@@+",
2533 "=$$$$*&&&&&&&&&@@@@+",
2534 "$&&&&&&&&&&&&&&@@@@+",
2535 "$&&&&&&&&&&&&&&@@@@+",
2536 "$&&&&&&&&&&&&&&@@@@+",
2537 "=$$$$$$$$$$$$$$++++."
2541 signal_xpm_low = [
2542 "20 20 8 1",
2543 " c None",
2544 ". c #C6C6C6",
2545 "+ c #CCCCCC",
2546 "@ c #DBDBDB",
2547 "# c #D3D3D3",
2548 "$ c #BFB0B5",
2549 "% c #C18799",
2550 "& c #C54F74",
2551 " .+++.",
2552 " +@@@+",
2553 " +@@@+",
2554 " +@@@+",
2555 " +@@@+",
2556 " .++++#@@@+",
2557 " +@@@@@@@@+",
2558 " +@@@@@@@@+",
2559 " +@@@@@@@@+",
2560 " +@@@@@@@@+",
2561 " .++++#@@@@@@@@+",
2562 " +@@@@@@@@@@@@@+",
2563 " +@@@@@@@@@@@@@+",
2564 " +@@@@@@@@@@@@@+",
2565 " +@@@@@@@@@@@@@+",
2566 "$%%%%#@@@@@@@@@@@@@+",
2567 "%&&&&@@@@@@@@@@@@@@+",
2568 "%&&&&@@@@@@@@@@@@@@+",
2569 "%&&&&@@@@@@@@@@@@@@+",
2570 "$%%%%++++++++++++++."
2574 ####################################################################################################
2575 # Make so we can be imported
2576 if __name__ == "__main__":
2577 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
2578 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
2579 else:
2580 import gtk, gobject
2581 gtk.gdk.threads_init()
2582 apQueue = Queue.Queue(100)
2583 commQueue = Queue.Queue(2)
2584 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue ) ).start()
2585 main_radar_window = radar_window(confFile, apQueue, commQueue)
2586 gobject.timeout_add( 500, main_radar_window.update_plist_items )
2587 main_radar_window.main()