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