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