Add say() HappyDoc comments
[wifi-radar.git] / wifi-radar
blobb1aebe14425fcab846805430c7181661d381f87f
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 ):
161 try:
162 command = ' '.join(command)
163 return_code = call(command, shell=True)
164 if return_code >= 0:
165 return True
166 else:
167 print >>sys.stderr, "Child was terminated by signal", -return_code
168 except OSError, exception:
169 print >>sys.stderr, "Execution failed:", exception
170 return False
172 # Speak feedback message to user
174 #Parameters:
176 # 'words' -- string - Message to speak to user
178 #Returns:
180 # nothing
181 def say( words ):
182 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
183 words = words.replace( "\"", "\\\"" )
184 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
186 # Scan for a limited time, return ap names and bssid found
187 # Access points we find will be put on the outgoing Queue, apQueue
188 def scanning_thread( confFile, apQueue, commandQueue ):
189 # Setup our essid pattern matcher
190 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
191 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abg]+)", re.I | re.M | re.S )
192 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
193 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
194 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
195 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
197 access_points = {}
198 command = "scan"
199 while True:
200 try:
201 command = commandQueue.get_nowait()
202 if __debug__: print "scanning_thread received command:", command
203 command_read = True
204 except Queue.Empty:
205 command_read = False
206 if command == "scan":
207 if __debug__: print "Beginning scan pass"
208 # Some cards need to have the interface up to scan
209 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
210 # call ifconfig command and wait for return
211 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
212 # update the signal strengths
213 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
214 #if __debug__:
215 #print "Current IP ", get_current_ip()
216 #print "Current ESSID ", get_current_essid()
217 #print "Current BSSID ", get_current_bssid()
218 # zero out the signal levels for all access points
219 for bssid in access_points:
220 access_points[bssid]['signal'] = 0
221 # split the scan data based on the address line
222 hits = scandata.split(' - ')
223 for hit in hits:
224 # set the defaults for profile template
225 profile = get_new_profile()
226 m = essid_pattern.search( hit )
227 if m:
228 # we found an essid
229 profile['essid'] = m.groups()[1]
230 m = bssid_pattern.search( hit ) # get BSSID from scan
231 if m: profile['bssid'] = m.groups()[1]
232 m = protocol_pattern.search( hit ) # get protocol from scan
233 if m: profile['protocol'] = m.groups()[1]
234 m = mode_pattern.search( hit ) # get mode from scan
235 if m: profile['mode'] = m.groups()[1]
236 m = channel_pattern.search( hit ) # get channel from scan
237 if m: profile['channel'] = m.groups()[1]
238 m = enckey_pattern.search( hit ) # get encryption key from scan
239 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
240 m = signal_pattern.search( hit ) # get signal strength from scan
241 if m: profile['signal'] = m.groups()[1]
242 access_points[ profile['bssid'] ] = profile
243 for bssid in access_points:
244 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
245 # Put all, now or previously, sensed access_points into apQueue
246 try:
247 apQueue.put_nowait( access_points[bssid] )
248 except Queue.Full:
249 pass
250 elif command == "exit":
251 if __debug__: print "Exiting scanning_thread"
252 return
253 if command_read: commandQueue.task_done()
254 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
255 sleep( 30 )
256 else:
257 sleep( 1 )
260 ### ConnectionManager
261 ### class to manage connections
262 class ConnectionManager():
263 def __init__( self, confFile, commandQueue ):
264 self.confFile = confFile
265 self.commQueue = commandQueue
266 # is connection running?
267 self.state = False
268 if self.get_current_ip():
269 self.state = True
270 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
272 def if_change( self, state ):
273 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
274 # call ifconfig command and wait for return
275 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
277 def connect_to_network( self, profile, status_window ):
278 self.profile = profile
279 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
280 say( msg )
281 if __debug__: print " %s" % msg
282 # ready to dance
283 # Let's run the connection prescript
284 if self.profile['con_prescript'].strip() != '':
285 # got something to execute
286 # run connection prescript through shell and wait for return
287 if __debug__: print "executing connection prescript:", self.profile['con_prescript']
288 shellcmd([self.profile['con_prescript']])
289 if status_window:
290 status_window.run()
291 # Some cards need to have the interface up
292 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
293 self.if_change('up')
294 # Start building iwconfig command line, command
295 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
296 iwconfig_command.append( confFile.get_opt('DEFAULT.interface') )
297 # Setting essid
298 iwconfig_command.append( 'essid' )
299 iwconfig_command.append( self.profile['essid'] )
300 # Setting nick
301 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
302 # Setting key
303 iwconfig_command.append( 'key' )
304 if self.profile['key'] == '':
305 iwconfig_command.append( 'off' )
306 else:
307 iwconfig_command.append( self.profile['key'] )
308 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
309 # Setting mode to Master, this will cause association with the AP to fail
310 #iwconfig_command.append( 'mode' )
311 #iwconfig_command.append( 'Master' )
312 # Setting channel
313 if self.profile['channel'] != '':
314 iwconfig_command.append( 'channel' )
315 iwconfig_command.append( self.profile['channel'] )
316 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
317 iwconfig_command.append( 'ap' )
318 iwconfig_command.append( self.profile['bssid'] )
319 # Some cards require a commit
320 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
321 if __debug__: print 'iwconfig_args %s ' % ( iwconfig_args, )
322 iwconfig_command.append( 'commit' )
323 # call iwconfig command and wait for return
324 if not shellcmd(iwconfig_command): return
325 # Now normal network stuff
326 # Kill off any existing DHCP clients running
327 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
328 if __debug__: print "Killing existing DHCP..."
329 try:
330 if self.confFile.get_opt('DHCP.kill_args') != '':
331 # call DHCP client kill command and wait for return
332 if __debug__: print self.confFile.get_opt('DHCP.command') + " " + self.confFile.get_opt('DHCP.kill_args')
333 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
334 else:
335 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
336 except OSError:
337 print "failed to kill DHCP client"
338 sys.exit()
339 finally:
340 print "Stale pid file. Removing..."
341 os.remove(self.confFile.get_opt('DHCP.pidfile'))
342 # Kill off any existing WPA supplicants running
343 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
344 if __debug__: print "Killing existing WPA supplicant..."
345 try:
346 if not self.confFile.get_opt('WPA.kill_command') != '':
347 # call WPA supplicant kill command and wait for return
348 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
349 else:
350 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
351 except OSError:
352 print "failed to kill WPA supplicant"
353 sys.exit()
354 finally:
355 print "Stale pid file. Removing..."
356 os.remove(self.confFile.get_opt('WPA.pidfile'))
357 # Begin WPA supplicant
358 if self.profile['use_wpa'] :
359 if __debug__: print "WPA args: %s" % ( wpa_options, )
360 if status_window:
361 status_window.update_message("WPA supplicant starting")
362 # call WPA supplicant command and do not wait for return
363 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
364 if self.profile['use_dhcp'] :
365 if __debug__: print "Disable iwlist while dhcp in progress..."
366 try:
367 self.commQueue.put("pause")
368 except Queue.Full:
369 pass
370 if status_window:
371 status_window.update_message("Acquiring IP Address (DHCP)")
372 # call DHCP client command and do not wait for return
373 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
374 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
375 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
376 dhcp_proc = Popen(dhcp_command, stdout=None)
377 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
378 tick = 0.25
379 waiting = dhcp_proc.poll()
380 while waiting == None:
381 waiting = dhcp_proc.poll()
382 if timer < 0:
383 os.kill(dhcp_proc.pid, SIGTERM)
384 break
385 if sys.modules.has_key("gtk"):
386 while gtk.events_pending():
387 gtk.main_iteration(False)
388 timer -= tick
389 sleep(tick)
390 if not self.get_current_ip():
391 if status_window:
392 status_window.update_message("Could not get IP address!")
393 if sys.modules.has_key("gtk"):
394 while gtk.events_pending():
395 gtk.main_iteration(False)
396 sleep(3)
397 else:
398 print "Could not get IP address!"
399 self.disconnect_interface()
400 else:
401 if status_window:
402 status_window.update_message("Got IP address. Done.")
403 self.state = True
404 if sys.modules.has_key("gtk"):
405 while gtk.events_pending():
406 gtk.main_iteration(False)
407 sleep(2)
408 # Re-enable iwlist
409 try:
410 self.commQueue.put("scan")
411 except Queue.Full:
412 pass
413 else:
414 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'] )
415 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
416 resolv_contents = ''
417 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
418 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
419 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
420 if ( resolv_contents != '' ):
421 resolv_file=open('/etc/resolv.conf', 'w')
422 resolv_file.write(s)
423 resolv_file.close
424 if not shellcmd([ifconfig_command]): return
425 if not shellcmd([route_command]): return
426 self.state = True
427 # Let's run the connection postscript
428 con_postscript = self.profile['con_postscript']
429 if self.profile['con_postscript'].strip() != '':
430 if __debug__: print "executing connection postscript:", self.profile['con_postscript']
431 shellcmd([self.profile['con_postscript']])
432 if status_window:
433 status_window.destroy()
435 def disconnect_interface( self ):
436 msg = "Disconnecting"
437 say( msg )
438 if __debug__: print msg
439 # Pause scanning while manipulating card
440 try:
441 self.commQueue.put("pause")
442 except Queue.Full:
443 pass
444 if self.state:
445 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
446 # Let's run the disconnection prescript
447 if self.profile['dis_prescript'].strip() != '':
448 if __debug__: print "executing disconnection prescript:", self.profile['dis_prescript']
449 shellcmd([self.profile['dis_prescript']])
450 if __debug__: print "Kill off any existing DHCP clients running..."
451 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
452 if __debug__: print "Killing existing DHCP..."
453 try:
454 if self.confFile.get_opt('DHCP.kill_args') != '':
455 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
456 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
457 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
458 if __debug__: print "DHCP command", dhcp_command
459 # call DHCP client command and wait for return
460 if not shellcmd(dhcp_command): return
461 else:
462 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
463 except OSError:
464 print "failed to kill DHCP client"
465 if __debug__: print "Kill off any existing WPA supplicants running..."
466 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
467 if __debug__: print "Killing existing WPA supplicant..."
468 try:
469 if not self.confFile.get_opt('WPA.kill_command') != '':
470 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
471 if not shellcmd(wpa_command): return
472 else:
473 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
474 except OSError:
475 print "failed to kill WPA supplicant"
476 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
477 if __debug__: print "Let's clear out the wireless stuff"
478 if __debug__: print 'Now take the interface down'
479 self.if_change('down')
480 if __debug__: print 'Since it may be brought back up by the next scan, lets unset its IP'
481 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
482 # Let's run the disconnection postscript
483 if self.profile['dis_postscript'].strip() != '':
484 if __debug__: print "executing disconnection postscript:", self.profile['dis_postscript']
485 shellcmd([self.profile['dis_postscript']])
486 self.state = False
487 if __debug__: print 'Disconnect complete.'
488 # Begin scanning again
489 try:
490 self.commQueue.put("scan")
491 except Queue.Full:
492 pass
494 def get_current_ip( self ):
495 """Returns the current IP if any by calling ifconfig"""
496 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
497 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
498 # Be careful to the language (inet adr: in French for example)
500 # Hi Brian
502 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
503 # There the string in ifconfig is inet Adresse for the IP which isn't
504 # found by the current get_current_ip function in wifi-radar. I changed
505 # the according line (#289; gentoo, v1.9.6-r1) to
506 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
507 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
509 # I'd be happy if you could incorporate this small change because as now
510 # I've got to change the file every time it is updated.
512 # Best wishes
514 # Simon
515 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
516 line = ifconfig_info.read()
517 if ip_re.search( line ):
518 return ip_re.search( line ).group(1)
519 return None
521 def get_current_essid( self ):
522 """Returns the current ESSID if any by calling iwconfig"""
523 iwconfig_info = Popen([confFile.get_opt('DEFAULT.iwconfig_command'), confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
524 # Be careful to the language (inet adr: in French for example)
525 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
526 line = iwconfig_info.read()
527 if essid_re.search( line ):
528 return essid_re.search( line ).group(2)
529 return None
531 def get_current_bssid( self ):
532 """Returns the current BSSID if any by calling iwconfig"""
533 iwconfig_info = Popen([confFile.get_opt('DEFAULT.iwconfig_command'), confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
534 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
535 line = iwconfig_info.read()
536 if bssid_re.search( line ):
537 return bssid_re.search( line ).group(2)
538 return None
542 ##################
543 # The main window
544 class radar_window:
545 def __init__( self, confFile, apQueue, commQueue ):
546 # create the main window and connect the normal events
547 global signal_xpm_none
548 global signal_xpm_low
549 global signal_xpm_barely
550 global signal_xpm_ok
551 global signal_xpm_best
552 global known_profile_icon
553 global unknown_profile_icon
554 global wifi_radar_icon
556 self.confFile = confFile
557 self.apQueue = apQueue
558 self.commandQueue = commQueue
559 self.access_points = {}
560 self.connection = None
562 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
563 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
564 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
565 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
566 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
567 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
568 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
569 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
570 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
571 self.window.set_icon( icon )
572 self.window.set_border_width( 10 )
573 self.window.set_size_request( 550, 300 )
574 self.window.set_title( "WiFi Radar" )
575 self.window.connect( 'delete_event', self.delete_event )
576 self.window.connect( 'destroy', self.destroy, )
577 # let's create all our widgets
578 self.current_network = gtk.Label()
579 self.current_network.show()
580 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
581 self.close_button.show()
582 self.close_button.connect( 'clicked', self.delete_event, None )
583 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
584 self.preferences_button.show()
585 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
586 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
587 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
588 self.plist = gtk.TreeView( self.pstore )
589 # The icons column, known and encryption
590 self.pix_cell = gtk.CellRendererPixbuf()
591 self.wep_cell = gtk.CellRendererPixbuf()
592 self.icons_cell = gtk.CellRendererText()
593 self.icons_col = gtk.TreeViewColumn()
594 self.icons_col.pack_start( self.pix_cell, False )
595 self.icons_col.pack_start( self.wep_cell, False )
596 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
597 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
598 self.plist.append_column( self.icons_col )
599 # The AP column
600 self.ap_cell = gtk.CellRendererText()
601 self.ap_col = gtk.TreeViewColumn( "Access Point" )
602 self.ap_col.pack_start( self.ap_cell, True )
603 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
604 self.plist.append_column( self.ap_col )
605 # The signal column
606 self.sig_cell = gtk.CellRendererPixbuf()
607 self.signal_col = gtk.TreeViewColumn( "Signal" )
608 self.signal_col.pack_start( self.sig_cell, True )
609 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
610 self.plist.append_column( self.signal_col )
611 # The mode column
612 self.mode_cell = gtk.CellRendererText()
613 self.mode_col = gtk.TreeViewColumn( "Mode" )
614 self.mode_col.pack_start( self.mode_cell, True )
615 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
616 self.plist.append_column( self.mode_col )
617 # The protocol column
618 self.prot_cell = gtk.CellRendererText()
619 self.protocol_col = gtk.TreeViewColumn( "802.11" )
620 self.protocol_col.pack_start( self.prot_cell, True )
621 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
622 self.plist.append_column( self.protocol_col )
623 # The channel column
624 self.channel_cell = gtk.CellRendererText()
625 self.channel_col = gtk.TreeViewColumn( "Channel" )
626 self.channel_col.pack_start( self.channel_cell, True )
627 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
628 self.plist.append_column( self.channel_col )
629 # DnD Ordering
630 self.plist.set_reorderable( True )
631 self.pstore.connect( 'row-changed', self.update_auto_profile_order )
632 # enable/disable buttons based on the selected network
633 self.selected_network = self.plist.get_selection()
634 self.selected_network.connect( 'changed', self.on_network_selection, None )
635 # the list scroll bar
636 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
637 sb.show()
638 self.plist.show()
639 # Add New button
640 self.new_button = gtk.Button( "_New" )
641 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
642 self.new_button.show()
643 # Add Configure button
644 self.edit_button = gtk.Button( "C_onfigure" )
645 self.edit_button.connect( 'clicked', self.edit_profile, None )
646 self.edit_button.show()
647 self.edit_button.set_sensitive(False)
648 # Add Delete button
649 self.delete_button = gtk.Button( "_Delete" )
650 self.delete_button.connect( 'clicked', self.delete_profile, None )
651 self.delete_button.show()
652 self.delete_button.set_sensitive(False)
653 # Add Connect button
654 self.connect_button = gtk.Button( "Co_nnect" )
655 self.connect_button.connect( 'clicked', self.connect_profile, None )
656 # Add Disconnect button
657 self.disconnect_button = gtk.Button( "D_isconnect" )
658 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
659 # lets add our widgets
660 rows = gtk.VBox( False, 3 )
661 net_list = gtk.HBox( False, 0 )
662 listcols = gtk.HBox( False, 0 )
663 prows = gtk.VBox( False, 0 )
664 # lets start packing
665 # the network list
666 net_list.pack_start( self.plist, True, True, 0 )
667 net_list.pack_start( sb, False, False, 0 )
668 # the rows level
669 rows.pack_start( net_list , True, True, 0 )
670 rows.pack_start( self.current_network, False, True, 0 )
671 # the list columns
672 listcols.pack_start( rows, True, True, 0 )
673 listcols.pack_start( prows, False, False, 5 )
674 # the list buttons
675 prows.pack_start( self.new_button, False, False, 2 )
676 prows.pack_start( self.edit_button, False, False, 2 )
677 prows.pack_start( self.delete_button, False, False, 2 )
678 prows.pack_end( self.connect_button, False, False, 2 )
679 prows.pack_end( self.disconnect_button, False, False, 2 )
681 self.window.action_area.pack_start( self.preferences_button )
682 self.window.action_area.pack_start( self.close_button )
684 rows.show()
685 prows.show()
686 listcols.show()
687 self.window.vbox.add( listcols )
688 self.window.vbox.set_spacing( 3 )
689 self.window.show_all()
691 # Now, immediately hide these two. The proper one will be
692 # displayed later, based on interface state. -BEF-
693 self.disconnect_button.hide()
694 self.connect_button.hide()
695 self.connect_button.set_sensitive(False)
697 # set up connection manager for later use
698 self.connection = ConnectionManager( self.confFile, self.commandQueue )
700 # Add our known profiles in order
701 for ap in self.confFile.auto_profile_order:
702 ap = ap.strip()
703 self.access_points[ ap ] = self.confFile.get_profile( ap )
704 wep = None
705 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
706 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'] ] )
707 # This is the first run (or, at least, no config file was present), so pop up the preferences window
708 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
709 self.confFile.remove_option('DEFAULT', 'new_file')
710 self.edit_preferences(self.preferences_button)
712 def main( self ):
713 gtk.main()
715 def destroy( self, widget = None):
716 self.confFile.write()
717 gtk.main_quit()
719 def delete_event( self, widget, event, data=None ):
720 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
721 try:
722 self.commandQueue.put("exit", True)
723 except Queue.Full:
724 pass
725 # Save the preferred networks order
726 self.update_auto_profile_order()
727 self.destroy()
728 return False
730 def update_plist_items( self ):
731 """ Updates the profiles list """
732 # Indicate to PyGtk that only one Gtk thread should run here
733 gtk.gdk.threads_enter()
734 # update the current ip and essid
735 # set the state of connect/disconnect buttons based on whether we have an IP address
736 if self.connection:
737 if self.connection.state:
738 self.current_network.set_text( "Connected to %s ip(%s)" % ( make_section_name( self.connection.get_current_essid(), self.connection.get_current_bssid() ), self.connection.get_current_ip() ) )
739 self.connect_button.hide()
740 self.disconnect_button.show()
741 else:
742 self.current_network.set_text( "Not Connected." )
743 self.disconnect_button.hide()
744 self.connect_button.show()
746 while True:
747 # Get profiles scanned by iwlist
748 try:
749 profile = self.apQueue.get_nowait()
750 except Queue.Empty:
751 break
752 else:
753 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
754 wep = None
755 if prow_iter != None:
756 # the AP is in the list of APs on the screen
757 apname = make_section_name(profile['essid'], profile['bssid'])
758 if self.access_points.has_key(apname):
759 # This AP has been configured and is/should be stored in the config file
760 profile['known'] = self.access_points[apname]['known']
761 self.access_points[apname]['available'] = profile['available']
762 self.access_points[apname]['encrypted'] = profile['encrypted']
763 self.access_points[apname]['signal'] = profile['signal']
764 self.access_points[apname]['mode'] = profile['mode']
765 self.access_points[apname]['protocol'] = profile['protocol']
766 self.access_points[apname]['channel'] = profile['channel']
767 # Set the 'known' values; False is default, overridden to True by self.access_points
768 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
769 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
770 self.pstore.set_value(prow_iter, 3, profile['available'])
771 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
772 self.pstore.set_value(prow_iter, 4, wep)
773 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
774 self.pstore.set_value(prow_iter, 6, profile['mode'])
775 self.pstore.set_value(prow_iter, 7, profile['protocol'])
776 self.pstore.set_value(prow_iter, 8, profile['channel'])
777 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
778 #for val in self.pstore[prow_iter]:
779 #print val,
780 else:
781 # the AP is not in the list of APs on the screen
782 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'] ] )
783 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
784 # Allow other Gtk threads to run
785 gtk.gdk.threads_leave()
786 #print "update_plist_items: Empty apQueue"
787 return True
789 def pixbuf_from_known( self, known ):
790 """ return the proper icon for value of known """
791 if known:
792 return self.known_profile_icon
793 else:
794 return self.unknown_profile_icon
796 def pixbuf_from_signal( self, signal ):
797 signal = int( signal )
798 #print "signal level:", signal
799 if signal < 3:
800 return self.signal_none_pb
801 elif signal < 12:
802 return self.signal_low_pb
803 elif signal < 20:
804 return self.signal_barely_pb
805 elif signal < 35:
806 return self.signal_ok_pb
807 elif signal >= 35:
808 return self.signal_best_pb
809 else:
810 return None
812 def get_row_by_ap( self, essid, bssid ):
813 for row in self.pstore:
814 if ( row[0] == essid + "\n" + bssid ):
815 #print "matched:", row.iter, essid, bssid
816 return row.iter
817 return None
819 # enable/disable buttons based on the selected network
820 def on_network_selection( self, widget, data=None ):
821 ( store, selected_iter ) = self.selected_network.get_selected()
822 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
823 # if no networks are selected, disable all buttons except New
824 # (this occurs after a drag-and-drop)
825 if selected_iter == None:
826 self.edit_button.set_sensitive(False)
827 self.delete_button.set_sensitive(False)
828 self.connect_button.set_sensitive(False)
829 return
830 # enable/disable buttons
831 self.connect_button.set_sensitive(True)
832 if (store.get_value(selected_iter, 2) == True): # is selected network known?
833 self.edit_button.set_sensitive(True)
834 self.delete_button.set_sensitive(True)
835 else:
836 self.edit_button.set_sensitive(True)
837 self.delete_button.set_sensitive(False)
839 # init and run the preferences dialog
840 def edit_preferences( self, widget, data=None ):
841 p = preferences_dialog( self, self.confFile )
842 ok = p.run()
843 p.destroy()
845 def create_new_profile( self, widget, profile, data=None ):
846 profile_editor = profile_dialog( self, profile )
847 profile = profile_editor.run()
848 profile_editor.destroy()
849 if profile:
850 apname = make_section_name( profile['essid'], profile['bssid'] )
851 # Check that the ap does not exist already
852 if apname in self.confFile.profiles():
853 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) )
854 dlg.run()
855 dlg.destroy()
856 del dlg
857 # try again
858 self.access_points[ apname ] = profile
859 self.confFile.set_section( apname, profile )
860 # if it is not in the auto_profile_order add it
861 if not apname in self.confFile.auto_profile_order:
862 self.confFile.auto_profile_order.append(apname)
863 # add to the store
864 wep = None
865 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
866 return True
867 else:
868 # Did not create new profile
869 return False
871 def edit_profile( self, widget, data=None ):
872 ( store, selected_iter ) = self.plist.get_selection().get_selected()
873 if not selected_iter: return
874 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
875 apname = make_section_name( row_start[0], row_start[1] )
876 profile = self.confFile.get_profile( apname )
877 if profile:
878 profile_editor = profile_dialog( self, profile )
879 profile = profile_editor.run()
880 profile_editor.destroy()
881 if profile:
882 if __debug__:
883 print "Got edited profile ", profile
884 apname = make_section_name( profile['essid'], profile['bssid'] )
885 self.access_points[ apname ] = profile
886 self.confFile.set_section( apname, profile )
887 else:
888 profile = get_new_profile()
889 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
890 self.create_new_profile( widget, profile, data )
892 def delete_profile( self, widget, data=None ):
893 ( store, selected_iter ) = self.plist.get_selection().get_selected()
894 if not selected_iter: return
895 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
896 known = store.get_value( selected_iter, 1 )
897 if not known: return
898 dlg = gtk.MessageDialog(
899 self.window,
900 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
901 gtk.MESSAGE_QUESTION,
902 gtk.BUTTONS_YES_NO,
903 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
904 res = dlg.run()
905 dlg.destroy()
906 del dlg
907 if res == gtk.RESPONSE_NO: return
908 # Remove it
909 apname = make_section_name( essid, bssid )
910 del self.access_points[ apname ]
911 self.confFile.remove_section( apname )
912 print "delete_profile: ", apname, ":", self.confFile.auto_profile_order
913 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
914 self.pstore.remove( selected_iter )
915 # Let's save our current state
916 self.update_auto_profile_order()
918 def connect_profile( self, widget, profile, data=None ):
919 ( store, selected_iter ) = self.plist.get_selection().get_selected()
920 if not selected_iter: return
921 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
922 known = store.get_value( selected_iter, 2 )
923 if not known:
924 if data != 'noconnect':
925 dlg = gtk.MessageDialog(
926 self.window,
927 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
928 gtk.MESSAGE_QUESTION,
929 gtk.BUTTONS_YES_NO,
930 "This network does not have a profile configured.\n\nWould you like to create one now?" )
931 res = dlg.run()
932 dlg.destroy()
933 del dlg
934 if res == gtk.RESPONSE_NO: return
935 profile = get_new_profile()
936 profile['essid'] = essid
937 profile['bssid'] = bssid
938 if not self.create_new_profile( widget, profile, data ):
939 return
940 apname = make_section_name( essid, bssid )
941 self.connection.connect_to_network( self.access_points[apname], status_window( self ) )
943 def disconnect_profile( self, widget, data=None ):
944 self.connection.disconnect_interface()
946 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
947 # recreate the auto_profile_order
948 auto_profile_order = []
949 piter = self.pstore.get_iter_first()
950 while piter:
951 # only if it's known
952 if self.pstore.get_value( piter, 2 ) == True:
953 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
954 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
955 piter = self.pstore.iter_next( piter )
956 self.confFile.auto_profile_order = auto_profile_order
958 ###################################
959 # the preferences dialog
960 class preferences_dialog:
961 def __init__( self, parent, confFile ):
962 # set up the preferences window
963 global wifi_radar_icon
964 self.parent = parent
965 self.confFile = confFile
966 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
967 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
968 ( gtk.STOCK_CLOSE, True ) )
969 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
970 self.dialog.set_icon( icon )
971 self.dialog.set_resizable( True )
972 self.dialog.set_transient_for( self.parent.window )
973 self.tooltips = gtk.Tooltips()
975 # set up preferences widgets
978 # auto detect wireless device
979 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
981 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
983 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
984 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
985 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
986 self.dialog.vbox.pack_start(self.w_auto_detect, False, False, 5)
987 # network interface selecter
988 self.w_interface = gtk.combo_box_entry_new_text()
989 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
990 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
991 for device in wireless_devices:
992 if device != self.confFile.get_opt('DEFAULT.interface'):
993 self.w_interface.append_text(device)
994 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
995 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
996 self.w_interface.set_active(0)
997 self.w_interface_label = gtk.Label("Wireless device")
998 self.w_hbox1 = gtk.HBox(False, 0)
999 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1000 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1001 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1002 self.dialog.vbox.pack_start(self.w_hbox1, False, False, 5)
1003 # scan timeout (spin button of integers from 1 to 100)
1004 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,1 )
1005 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1006 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1007 self.w_scan_timeout.set_numeric(True)
1008 self.w_scan_timeout.set_snap_to_ticks(True)
1009 self.w_scan_timeout.set_wrap(False)
1010 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1011 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1012 self.w_hbox2 = gtk.HBox(False, 0)
1013 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1014 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1015 self.dialog.vbox.pack_start(self.w_hbox2, False, False, 5)
1016 # speak up
1017 self.w_speak_up = gtk.CheckButton("Use speak-up")
1018 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1019 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1020 self.dialog.vbox.pack_start(self.w_speak_up, False, False, 5)
1021 # commit required
1022 self.w_commit_required = gtk.CheckButton("Commit required")
1023 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1024 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1025 self.dialog.vbox.pack_start(self.w_commit_required, False, False, 5)
1026 # ifup required
1027 self.w_ifup_required = gtk.CheckButton("Ifup required")
1028 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1029 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1030 self.dialog.vbox.pack_start(self.w_ifup_required, False, False, 5)
1032 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1033 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1035 def run(self):
1036 self.dialog.show_all()
1037 return self.dialog.run()
1039 def destroy(self):
1040 # save preferences to config file
1041 if self.w_auto_detect.get_active():
1042 set_network_device("auto_detect")
1043 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1044 else:
1045 set_network_device(self.w_interface.child.get_text())
1046 self.confFile.set_opt('DEFAULT.interface', self.w_interface.child.get_text())
1047 # reconfigure the dhcp commands in case the network device was changed
1048 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1049 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1050 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1051 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1052 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
1053 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'up'])
1054 self.confFile.write()
1055 self.dialog.destroy()
1056 del self.dialog
1059 ###################################
1060 class profile_dialog:
1061 def __init__( self, parent, profile ):
1062 global wifi_radar_icon
1063 self.parent = parent
1064 self.profile = profile
1065 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1066 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1067 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1068 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1069 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1070 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1071 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1072 self.dialog.set_icon( icon )
1073 self.dialog.set_resizable( False )
1074 self.dialog.set_transient_for( self.parent.window )
1075 #self.dialog.set_size_request( 400, 400 )
1076 #################
1077 essid_table = gtk.Table( 1, 2, False )
1078 essid_table.set_row_spacings( 3 )
1079 essid_table.set_col_spacings( 3 )
1081 # The essid labels
1082 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1083 # The essid textboxes
1084 self.essid_entry = gtk.Entry( 32 )
1085 self.essid_entry.set_text( self.profile['essid'] )
1086 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1087 # Add the essid table to the dialog
1088 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1090 bssid_table = gtk.Table( 1, 2, False )
1091 bssid_table.set_row_spacings( 3 )
1092 bssid_table.set_col_spacings( 3 )
1093 # The bssid labels
1094 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1095 # The bssid textboxes
1096 self.bssid_entry = gtk.Entry( 32 )
1097 self.bssid_entry.set_text( self.profile['bssid'] )
1098 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1099 #self.key = gtk.Entry( 32 )
1100 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1101 # Add the bssid table to the dialog
1102 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1104 # create the wifi expander
1105 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1106 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1107 wifi_table = gtk.Table( 4, 2, False )
1108 wifi_table.set_row_spacings( 3 )
1109 wifi_table.set_col_spacings( 3 )
1110 # The Wifi labels
1111 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1112 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1113 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1114 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1115 # The Wifi text boxes
1116 self.mode_combo = gtk.combo_box_new_text()
1117 for mode in self.WIFI_MODES:
1118 self.mode_combo.append_text( mode )
1119 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1120 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1121 self.channel_combo = gtk.combo_box_new_text()
1122 for channel in self.WIFI_CHANNELS:
1123 self.channel_combo.append_text( channel )
1124 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1125 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1127 self.key_entry = gtk.Entry( 64 )
1128 self.key_entry.set_text( self.profile['key'] )
1129 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1131 self.security_combo = gtk.combo_box_new_text()
1132 for security in self.WIFI_SECURITY:
1133 self.security_combo.append_text( security )
1134 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1135 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1136 # Add the wifi table to the expander
1137 self.wifi_expander.add( wifi_table )
1138 # Add the expander to the dialog
1139 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1141 # create the wpa expander
1142 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1143 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1144 wpa_table = gtk.Table( 1, 2, False )
1145 wpa_table.set_row_spacings( 3 )
1146 wpa_table.set_col_spacings( 3 )
1147 # The labels
1148 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1149 # The text boxes
1150 self.wpa_driver_entry = gtk.Entry()
1151 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1152 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1153 # Add the wpa table to the expander
1154 self.wpa_expander.add( wpa_table )
1155 # Add the expander to the dialog
1156 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1158 # create the dhcp expander
1159 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1160 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1161 ip_table = gtk.Table( 6, 2, False )
1162 ip_table.set_row_spacings( 3 )
1163 ip_table.set_col_spacings( 3 )
1164 # The IP labels
1165 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1166 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1167 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1168 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1169 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1170 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1171 # The IP text boxes
1172 self.ip_entry = gtk.Entry( 15 )
1173 self.ip_entry.set_text( self.profile['ip'] )
1174 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1175 self.netmask_entry = gtk.Entry( 15 )
1176 self.netmask_entry.set_text( self.profile['netmask'] )
1177 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1178 self.gw_entry = gtk.Entry( 15 )
1179 self.gw_entry.set_text( self.profile['gateway'] )
1180 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1181 self.domain_entry = gtk.Entry( 32 )
1182 self.domain_entry.set_text( self.profile['domain'] )
1183 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1184 self.dns1_entry = gtk.Entry( 15 )
1185 self.dns1_entry.set_text( self.profile['dns1'] )
1186 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1187 self.dns2_entry = gtk.Entry( 15 )
1188 self.dns2_entry.set_text( self.profile['dns2'] )
1189 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1190 # Add the ip table to the expander
1191 self.dhcp_expander.add( ip_table )
1192 # Add the expander to the dialog
1193 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1195 # create the connection-building postpre expander
1196 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1197 con_pp_table = gtk.Table( 2, 2, False )
1198 con_pp_table.set_row_spacings( 3 )
1199 con_pp_table.set_col_spacings( 3 )
1200 # The labels
1201 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1202 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1203 # The text boxes
1204 self.con_prescript_entry = gtk.Entry()
1205 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1206 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1207 self.con_postscript_entry = gtk.Entry()
1208 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1209 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1210 # Add the pp table to the expander
1211 self.con_pp_expander.add( con_pp_table )
1212 # Add the expander to the dialog
1213 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1215 # create the disconnection postpre expander
1216 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1217 dis_pp_table = gtk.Table( 2, 2, False )
1218 dis_pp_table.set_row_spacings( 3 )
1219 dis_pp_table.set_col_spacings( 3 )
1220 # The labels
1221 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1222 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1223 # The text boxes
1224 self.dis_prescript_entry = gtk.Entry()
1225 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1226 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1227 self.dis_postscript_entry = gtk.Entry()
1228 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1229 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1230 # Add the pp table to the expander
1231 self.dis_pp_expander.add( dis_pp_table )
1232 # Add the expander to the dialog
1233 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1235 def run( self ):
1236 self.dialog.show_all()
1237 if self.dialog.run():
1238 self.profile['known'] = True
1239 self.profile['essid'] = self.essid_entry.get_text().strip()
1240 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1241 self.profile['key'] = self.key_entry.get_text().strip()
1242 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1243 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1244 self.profile['encrypted'] = ( self.profile['security'] != '' )
1245 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1246 self.profile['protocol'] = 'g'
1247 self.profile['available'] = ( self.profile['signal'] > 0 )
1248 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1249 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1250 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1251 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1252 # wpa
1253 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1254 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1255 # dhcp
1256 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1257 self.profile['ip'] = self.ip_entry.get_text().strip()
1258 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1259 self.profile['gateway'] = self.gw_entry.get_text().strip()
1260 self.profile['domain'] = self.domain_entry.get_text().strip()
1261 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1262 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1263 return self.profile
1264 return None
1266 def destroy( self ):
1267 self.dialog.destroy()
1268 del self.dialog
1270 def toggle_use_dhcp( self, widget, data = None ):
1271 expanded = self.dhcp_expander.get_expanded()
1272 if expanded:
1273 self.dhcp_expander.set_label( USE_IP_LABEL )
1274 else:
1275 self.dhcp_expander.set_label( USE_DHCP_LABEL )
1277 def toggle_use_wpa( self, widget, data = None ):
1278 expanded = self.wpa_expander.get_expanded()
1279 if expanded:
1280 self.wpa_expander.set_label( USE_WPA_LABEL )
1281 else:
1282 self.wpa_expander.set_label( NO_WPA_LABEL )
1284 def get_array_index( self, item, array ):
1285 try:
1286 return array.index( item.strip() )
1287 except:
1288 pass
1289 return 0
1291 def get_array_item( self, index, array ):
1292 try:
1293 return array[ index ]
1294 except:
1295 pass
1296 return ''
1298 ### status_window
1300 ### A simple class for putting up a "Please wait" dialog so the user
1301 ### doesn't think we've forgotten about them.
1302 class status_window:
1303 def __init__( self, parent ):
1304 global wifi_radar_icon
1305 self.parent = parent
1306 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
1307 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1308 self.dialog.set_icon( icon )
1309 self.lbl = gtk.Label("Please wait...")
1310 self.bar = gtk.ProgressBar()
1311 self.dialog.vbox.pack_start(self.lbl)
1312 self.dialog.vbox.pack_start(self.bar)
1313 self.dialog.show_all()
1315 def update_message( self, message ):
1316 self.lbl.set_text(message)
1318 def inhibit_close( self, widget, signal ):
1319 return True
1321 def update_window( self ):
1322 # Do stuff
1323 self.bar.pulse()
1324 return True
1326 def run( self ):
1327 self.dialog.show_all()
1328 self.timer = gobject.timeout_add(250,self.update_window)
1329 return
1331 def destroy( self ):
1332 gobject.source_remove(self.timer)
1333 self.dialog.destroy()
1334 del self.dialog
1336 ### ConfigFile
1337 ### class to manage the configuration file
1338 class ConfigFile(ConfigParser.SafeConfigParser):
1339 """ class to manage the configuration file """
1340 def __init__( self, filename, defaults ):
1341 self.filename = filename
1342 self.auto_profile_order = []
1343 ConfigParser.SafeConfigParser.__init__(self, defaults)
1345 def set_section( self, section_name, section_dict ):
1346 """ set the contents of a section to values from a dictionary """
1347 try:
1348 self.add_section(section_name)
1349 except ConfigParser.DuplicateSectionError:
1350 pass
1351 for key in section_dict.keys():
1352 if type(section_dict[key]) == BooleanType:
1353 self.set_bool_opt(section_name + "." + key, section_dict[key])
1354 elif type(section_dict[key]) == IntType:
1355 self.set_int_opt(section_name + "." + key, section_dict[key])
1356 elif type(section_dict[key]) == FloatType:
1357 self.set_float_opt(section_name + "." + key, section_dict[key])
1358 else:
1359 self.set_opt(section_name + "." + key, section_dict[key])
1361 def get_profile( self, section_name ):
1362 """ return the profile recorded in the specified section """
1363 if section_name in self.profiles():
1364 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' ]
1365 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
1366 int_types = [ 'signal' ]
1367 profile = {}
1368 for option in bool_types:
1369 profile[option] = self.get_opt_as_bool( section_name + "." + option )
1370 for option in int_types:
1371 profile[option] = self.get_opt_as_int( section_name + "." + option )
1372 for option in str_types:
1373 profile[option] = self.get_opt( section_name + "." + option )
1374 return profile
1375 return None
1377 def get_opt( self, option_path ):
1378 """ get a config option and handle exceptions """
1379 #print "ConfigFile.get_opt: ", option_path
1380 (section, option) = option_path.split('.')
1381 try:
1382 return self.get(section, option)
1383 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
1384 return None
1386 def get_opt_as_bool( self, option_path ):
1387 """ get a config option and return as a boolean type """
1388 option = self.get_opt(option_path)
1389 if isinstance(option, BooleanType) or isinstance(option, NoneType):
1390 return option
1391 if option == 'True':
1392 return True
1393 if option == 'False':
1394 return False
1395 raise ValueError, 'boolean option was not True or False'
1397 def get_opt_as_int( self, option_path ):
1398 """ get a config option and return as an integer type """
1399 return int(float(self.get_opt(option_path)))
1401 def set_bool_opt( self, option_path, value ):
1402 """ convert boolean type to string and set config option """
1403 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
1404 value == 'True'
1405 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
1406 value == 'False'
1407 else:
1408 raise ValueError, 'cannot convert value to string'
1409 self.set_opt(option_path, repr(value))
1411 def set_int_opt( self, option_path, value ):
1412 """ convert integer type to string and set config option """
1413 if not isinstance(value, IntType):
1414 raise ValueError, 'value is not an integer'
1415 self.set_opt(option_path, repr(value))
1417 def set_float_opt( self, option_path, value ):
1418 """ convert float type to string and set config option """
1419 if not isinstance(value, FloatType):
1420 raise ValueError, 'value is not a float'
1421 self.set_opt(option_path, repr(int(value)))
1423 def set_opt( self, option_path, value ):
1424 """ set a config option and handle exceptions """
1425 (section, option) = option_path.split('.')
1426 try:
1427 self.set(section, option, value)
1428 except ConfigParser.NoSectionError:
1429 self.add_section(section)
1430 self.set_opt(option_path, value)
1432 def profiles( self ):
1433 """ return a list of the section names which denote AP profiles """
1434 profile_list = []
1435 for section in self.sections():
1436 if ':' in section:
1437 profile_list.append(section)
1438 return profile_list
1440 def read ( self ):
1441 fp = open( self.filename, "r" )
1442 self.readfp(fp)
1443 # convert the auto_profile_order to a list for ordering
1444 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
1445 for ap in self.profiles():
1446 self.set_bool_opt( ap + '.known', True)
1447 if ap in self.auto_profile_order: continue
1448 self.auto_profile_order.append( ap )
1450 def write( self ):
1451 """ Copied from ConfigParser and modified to write options in alphabetical order """
1452 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
1453 fp = open( self.filename, "w" )
1454 # write DEFAULT section first
1455 if self._defaults:
1456 fp.write("[DEFAULT]\n")
1457 for key in sorted(self._defaults.keys()):
1458 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
1459 fp.write("\n")
1460 # write non-profile sections first
1461 for section in self._sections:
1462 if not section in self.profiles():
1463 fp.write("[%s]\n" % section)
1464 for key in sorted(self._sections[section].keys()):
1465 if key != "__name__":
1466 fp.write("%s = %s\n" %
1467 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
1468 fp.write("\n")
1469 # write profile sections
1470 for section in self._sections:
1471 if section in self.profiles():
1472 fp.write("[%s]\n" % section)
1473 for key in sorted(self._sections[section].keys()):
1474 if key != "__name__":
1475 fp.write("%s = %s\n" %
1476 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
1477 fp.write("\n")
1480 ####################################################################################################
1481 # Speaking up
1482 def say( words ):
1483 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
1484 words = words.replace( "\"", "\\\"" )
1485 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
1488 # load our conf file and known profiles
1489 ####################################################################################################
1490 # Defaults, these may get overridden by values found in the conf file.
1491 # The network interface you use.
1492 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
1493 config_defaults = { 'interface': "auto_detect",
1494 # How long should the scan for access points last?
1495 'scan_timeout': '5',
1496 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
1497 # Set the speak_up option to false if you do not have or want this.
1498 'speak_command': '/usr/bin/say',
1499 # Should I speak up when connecting to a network? (If you have a speech command)
1500 'speak_up': 'False',
1501 # You may set this to true for cards that require a "commit" command with iwconfig
1502 'commit_required': 'False',
1503 # You may set this to true for cards that require the interface to be brought up first
1504 'ifup_required': 'False',
1505 # Set the location of several important programs
1506 'iwlist_command': '/sbin/iwlist',
1507 'iwconfig_command': '/sbin/iwconfig',
1508 'ifconfig_command': '/sbin/ifconfig',
1509 'route_command': '/sbin/route',
1510 'auto_profile_order': '[]',
1511 'version': WIFI_RADAR_VERSION }
1513 config_dhcp = { 'command': 'dhcpcd',
1514 'timeout': '30',
1515 'args': '-D -o -i dhcp_client -t %(timeout)s',
1516 'kill_args': '-k',
1517 'pidfile': '/var/run/dhcpcd-%(interface)s.pid' }
1519 config_wpa = { 'command': '/usr/sbin/wpa_supplicant',
1520 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
1521 'kill_command': '',
1522 'configuration': '/etc/wpa_supplicant.conf',
1523 'driver': 'wext',
1524 'pidfile': '/var/run/wpa_supplicant.pid' }
1526 # initialize config, with defaults
1527 confFile = ConfigFile(CONF_FILE, config_defaults)
1528 confFile.set_section("DHCP", config_dhcp)
1529 confFile.set_section("WPA", config_wpa)
1531 if not os.path.isfile( CONF_FILE ):
1532 confFile.set_bool_opt('DEFAULT.new_file', True)
1533 else:
1534 if not os.access(CONF_FILE, os.R_OK):
1535 print "Can't open " + CONF_FILE + "."
1536 print "Are you root?"
1537 sys.exit()
1538 confFile.read()
1541 # First, we add our known profiles
1542 for apname in confFile.profiles():
1543 ap = get_new_profile()
1544 (ap['essid'], ap['bssid']) = split_section_name( apname )
1545 # read the important values
1546 for key in ap.keys():
1547 ap[key] = confFile.get_opt(apname + "." + key)
1548 # if it is not in the auto_profile_order add it
1549 if not apname in confFile.auto_profile_order:
1550 confFile.auto_profile_order.append( apname )
1552 #set_network_device(confFile.get_opt('DEFAULT.interface'))
1554 ####################################################################################################
1555 # Embedded Images
1556 wifi_radar_icon = [ ""
1557 "GdkP"
1558 "\0\0\22""7"
1559 "\2\1\0\2"
1560 "\0\0\1\214"
1561 "\0\0\0c"
1562 "\0\0\0O"
1563 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
1564 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
1565 "\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"
1566 "\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"
1567 "\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"
1568 "\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"
1569 "\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"
1570 "\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"
1571 "\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"
1572 "\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"
1573 "\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"
1574 "\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"
1575 "\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"
1576 "\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"
1577 "\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"
1578 "\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"
1579 "\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"
1580 "\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"
1581 "\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"
1582 "\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"
1583 "\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"
1584 "\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"
1585 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
1586 "\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"
1587 "\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"
1588 "\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"
1589 "\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"
1590 "\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"
1591 "\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"
1592 "\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"
1593 "\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"
1594 "\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"
1595 "\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"
1596 "\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"
1597 "\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"
1598 "\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"
1599 "\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"
1600 "\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"
1601 "\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"
1602 "\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"
1603 "\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"
1604 "\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"
1605 "\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"
1606 "\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"
1607 "\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"
1608 "\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"
1609 "\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"
1610 "\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"
1611 "\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"
1612 "\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"
1613 "\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"
1614 "\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"
1615 "\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"
1616 "\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"
1617 "\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"
1618 "\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"
1619 "\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"
1620 "\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"
1621 "\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"
1622 "\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"
1623 "\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"
1624 "\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"
1625 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
1626 "\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"
1627 "\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"
1628 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
1629 "\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"
1630 "\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"
1631 "\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"
1632 "\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"
1633 "\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"
1634 "\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"
1635 "\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"
1636 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
1637 "\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"
1638 "\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"
1639 "\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"
1640 "\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"
1641 "\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"
1642 "\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"
1643 "|\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"
1644 "\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"
1645 "\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"
1646 "\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"
1647 "\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"
1648 "\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"
1649 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
1650 "\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"
1651 "\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"
1652 "\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"
1653 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
1654 "\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"
1655 "\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"
1656 "\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"
1657 "\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"
1658 "\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"
1659 "\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"
1660 "\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"
1661 "\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"
1662 "\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"
1663 "\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"
1664 "\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"
1665 "\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"
1666 "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"
1667 "\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"
1668 "\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"
1669 "\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"
1670 "\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"
1671 "\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"
1672 "\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"
1673 "\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"
1674 "\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"
1675 "\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"
1676 "\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"
1677 "\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"
1678 "\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"
1679 "\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"
1680 "\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"
1681 "\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|"
1682 "\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"
1683 "\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"
1684 "\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"
1685 "\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"
1686 "\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"
1687 "\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"
1688 "\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"
1689 "\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"
1690 "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"
1691 "\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"
1692 "\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"
1693 "\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"
1694 "\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"
1695 "\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"
1696 "\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"
1697 "\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"
1698 "\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"
1699 "\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"
1700 "\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"
1701 "\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"
1702 "\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"
1703 "\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"
1704 "\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"
1705 "\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"
1706 "\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"
1707 "\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"
1708 "\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"
1709 "\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"
1710 "\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"
1711 "\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"
1712 "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"
1713 "\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"
1714 "\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"
1715 "\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"
1716 "\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"
1717 "\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"
1718 "\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"
1719 "\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"
1720 "\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"
1721 "\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"
1722 "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"
1723 "\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"
1724 "\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"
1725 "\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"
1726 "\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"
1727 "\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"
1728 "\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"
1729 "\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"
1730 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
1731 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
1732 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
1733 "\0"]
1735 known_profile_icon = [ ""
1736 "GdkP"
1737 "\0\0\5""0"
1738 "\2\1\0\2"
1739 "\0\0\0P"
1740 "\0\0\0\24"
1741 "\0\0\0\24"
1742 "\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"
1743 "\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"
1744 "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"
1745 "\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"
1746 "\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"
1747 "\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"
1748 "\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"
1749 "\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"
1750 "\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"
1751 "\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"
1752 "\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"
1753 "\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"
1754 "\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"
1755 "\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"
1756 "\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"
1757 "\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"
1758 "\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"
1759 "\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"
1760 "\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"
1761 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
1762 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
1763 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
1764 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
1765 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
1766 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
1767 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
1768 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
1769 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
1770 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
1771 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
1772 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
1773 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
1774 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
1775 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
1776 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
1777 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
1778 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
1779 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
1780 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
1781 "\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"
1782 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
1783 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
1784 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
1785 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
1786 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
1787 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
1788 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
1789 "\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"
1790 "\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"
1791 "\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"
1792 "\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"
1793 "\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"]
1795 unknown_profile_icon = [ ""
1796 "GdkP"
1797 "\0\0\5\22"
1798 "\2\1\0\2"
1799 "\0\0\0P"
1800 "\0\0\0\24"
1801 "\0\0\0\24"
1802 "\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"
1803 "\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"
1804 "\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"
1805 "\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"
1806 "(\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"
1807 "\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"
1808 "#\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"
1809 "\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"
1810 "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"
1811 "\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"
1812 "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"
1813 "\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"
1814 "\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"
1815 "\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"
1816 "\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"
1817 "\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"
1818 "\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"
1819 "\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"
1820 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
1821 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
1822 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
1823 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
1824 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
1825 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
1826 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
1827 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
1828 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
1829 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
1830 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
1831 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
1832 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
1833 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
1834 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
1835 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
1836 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
1837 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
1838 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
1839 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
1840 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
1841 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
1842 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
1843 "\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"
1844 "\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"
1845 "\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"
1846 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
1848 signal_xpm_barely = [
1849 "20 20 10 1",
1850 " c None",
1851 ". c #C6C6C6",
1852 "+ c #CCCCCC",
1853 "@ c #DBDBDB",
1854 "# c #D3D3D3",
1855 "$ c #A9B099",
1856 "% c #95A173",
1857 "& c #6B8428",
1858 "* c #B4B7AC",
1859 "= c #80924D",
1860 " .+++.",
1861 " +@@@+",
1862 " +@@@+",
1863 " +@@@+",
1864 " +@@@+",
1865 " .++++#@@@+",
1866 " +@@@@@@@@+",
1867 " +@@@@@@@@+",
1868 " +@@@@@@@@+",
1869 " +@@@@@@@@+",
1870 " $%%%%#@@@@@@@@+",
1871 " %&&&&@@@@@@@@@+",
1872 " %&&&&@@@@@@@@@+",
1873 " %&&&&@@@@@@@@@+",
1874 " %&&&&@@@@@@@@@+",
1875 "*%%%%=&&&&@@@@@@@@@+",
1876 "%&&&&&&&&&@@@@@@@@@+",
1877 "%&&&&&&&&&@@@@@@@@@+",
1878 "%&&&&&&&&&@@@@@@@@@+",
1879 "*%%%%%%%%%+++++++++."
1883 signal_xpm_best = [
1884 "20 20 6 1",
1885 " c None",
1886 ". c #9DAABF",
1887 "+ c #7B96BF",
1888 "@ c #386EBF",
1889 "# c #5982BF",
1890 "$ c #AEB4BF",
1891 " .+++.",
1892 " +@@@+",
1893 " +@@@+",
1894 " +@@@+",
1895 " +@@@+",
1896 " .++++#@@@+",
1897 " +@@@@@@@@+",
1898 " +@@@@@@@@+",
1899 " +@@@@@@@@+",
1900 " +@@@@@@@@+",
1901 " .++++#@@@@@@@@+",
1902 " +@@@@@@@@@@@@@+",
1903 " +@@@@@@@@@@@@@+",
1904 " +@@@@@@@@@@@@@+",
1905 " +@@@@@@@@@@@@@+",
1906 "$++++#@@@@@@@@@@@@@+",
1907 "+@@@@@@@@@@@@@@@@@@+",
1908 "+@@@@@@@@@@@@@@@@@@+",
1909 "+@@@@@@@@@@@@@@@@@@+",
1910 "$++++++++++++++++++."
1913 signal_xpm_none = [
1914 "20 20 6 1",
1915 " c None",
1916 ". c #C6C6C6",
1917 "+ c #CCCCCC",
1918 "@ c #DBDBDB",
1919 "# c #D3D3D3",
1920 "$ c #C2C2C2",
1921 " .+++.",
1922 " +@@@+",
1923 " +@@@+",
1924 " +@@@+",
1925 " +@@@+",
1926 " .++++#@@@+",
1927 " +@@@@@@@@+",
1928 " +@@@@@@@@+",
1929 " +@@@@@@@@+",
1930 " +@@@@@@@@+",
1931 " .++++#@@@@@@@@+",
1932 " +@@@@@@@@@@@@@+",
1933 " +@@@@@@@@@@@@@+",
1934 " +@@@@@@@@@@@@@+",
1935 " +@@@@@@@@@@@@@+",
1936 "$++++#@@@@@@@@@@@@@+",
1937 "+@@@@@@@@@@@@@@@@@@+",
1938 "+@@@@@@@@@@@@@@@@@@+",
1939 "+@@@@@@@@@@@@@@@@@@+",
1940 "$++++++++++++++++++."
1943 signal_xpm_ok = [
1944 "20 20 10 1",
1945 " c None",
1946 ". c #C6C6C6",
1947 "+ c #CCCCCC",
1948 "@ c #DBDBDB",
1949 "# c #A1A5B2",
1950 "$ c #848DA5",
1951 "% c #D3D3D3",
1952 "& c #4A5B8C",
1953 "* c #677498",
1954 "= c #B0B2B8",
1955 " .+++.",
1956 " +@@@+",
1957 " +@@@+",
1958 " +@@@+",
1959 " +@@@+",
1960 " #$$$$%@@@+",
1961 " $&&&&@@@@+",
1962 " $&&&&@@@@+",
1963 " $&&&&@@@@+",
1964 " $&&&&@@@@+",
1965 " #$$$$*&&&&@@@@+",
1966 " $&&&&&&&&&@@@@+",
1967 " $&&&&&&&&&@@@@+",
1968 " $&&&&&&&&&@@@@+",
1969 " $&&&&&&&&&@@@@+",
1970 "=$$$$*&&&&&&&&&@@@@+",
1971 "$&&&&&&&&&&&&&&@@@@+",
1972 "$&&&&&&&&&&&&&&@@@@+",
1973 "$&&&&&&&&&&&&&&@@@@+",
1974 "=$$$$$$$$$$$$$$++++."
1978 signal_xpm_low = [
1979 "20 20 8 1",
1980 " c None",
1981 ". c #C6C6C6",
1982 "+ c #CCCCCC",
1983 "@ c #DBDBDB",
1984 "# c #D3D3D3",
1985 "$ c #BFB0B5",
1986 "% c #C18799",
1987 "& c #C54F74",
1988 " .+++.",
1989 " +@@@+",
1990 " +@@@+",
1991 " +@@@+",
1992 " +@@@+",
1993 " .++++#@@@+",
1994 " +@@@@@@@@+",
1995 " +@@@@@@@@+",
1996 " +@@@@@@@@+",
1997 " +@@@@@@@@+",
1998 " .++++#@@@@@@@@+",
1999 " +@@@@@@@@@@@@@+",
2000 " +@@@@@@@@@@@@@+",
2001 " +@@@@@@@@@@@@@+",
2002 " +@@@@@@@@@@@@@+",
2003 "$%%%%#@@@@@@@@@@@@@+",
2004 "%&&&&@@@@@@@@@@@@@@+",
2005 "%&&&&@@@@@@@@@@@@@@+",
2006 "%&&&&@@@@@@@@@@@@@@+",
2007 "$%%%%++++++++++++++."
2009 signal_none_pb = None
2010 signal_low_pb = None
2011 signal_barely_pb= None
2012 signal_ok_pb = None
2013 signal_best_pb = None
2015 ####################################################################################################
2016 # Make so we can be imported
2017 if __name__ == "__main__":
2018 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
2019 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
2020 else:
2021 import gtk, gobject
2022 gtk.gdk.threads_init()
2023 apQueue = Queue.Queue(100)
2024 commQueue = Queue.Queue(2)
2025 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue ) ).start()
2026 main_radar_window = radar_window(confFile, apQueue, commQueue)
2027 gobject.timeout_add( 500, main_radar_window.update_plist_items )
2028 main_radar_window.main()