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