Set width for preferences dialog entry boxes
[wifi-radar.git] / wifi-radar
blob9b8447b2073a4f04fc04331beb60fefff4a43228
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
28 import gtk
29 import os
30 import Queue
31 import re
32 import string
33 import sys
34 import threading
35 from signal import SIGTERM
36 from subprocess import call, Popen, PIPE
37 from time import sleep
38 from types import *
40 WIFI_RADAR_VERSION = "0.0.0"
42 if __debug__:
43 print '__debug__ is True'
44 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
45 # turn on debugging.
48 # Where the conf file should live could be different for your distro. Please change
49 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
51 CONF_FILE = "/etc/wifi-radar.conf"
53 os.environ['LC_MESSAGES'] = 'C'
56 #####################################
57 # Labels
58 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
59 USE_IP_LABEL = "Manual network configuration"
60 WIFI_SET_LABEL = "WiFi Options"
61 CON_PP_LABEL = "Connection Commands"
62 DIS_PP_LABEL = "Disconnection Commands"
63 USE_WPA_LABEL = "Use WPA"
64 NO_WPA_LABEL = "No WPA"
65 ####################################################################################################
67 ####################################################################################################
68 ####################################################################################################
70 # Sets the interface to the specified network device
72 #Parameters:
74 # 'device' -- string - The network device to use
76 #Returns:
78 # nothing
79 def set_network_device( device ):
80 #print "set_network_device: ", device
81 if device != "auto_detect":
82 confFile.set_opt('DEFAULT.interface', device)
83 else: # auto detect network device
84 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
85 # If no devices are found, default to eth1.
86 # call iwconfig command and read output
87 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
88 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
89 if len(wireless_devices) > 0:
90 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
91 #else:
92 #print "No wifi-device found. Exiting."
93 #sys.exit()
95 # Return a blank profile
97 #Parameters:
99 # none
101 #Returns:
103 # dictionary -- An AP profile with defaults set.
104 def get_new_profile():
105 return { 'known': False,
106 'available': False,
107 'encrypted': False,
108 'essid': '',
109 'bssid': '',
110 'protocol': 'g',
111 'signal': 0,
112 'channel': 'auto',
113 'con_prescript': '',
114 'con_postscript': '',
115 'dis_prescript': '',
116 'dis_postscript': '',
117 'key': '',
118 'mode': '',
119 'security': '',
120 'use_wpa': False,
121 'wpa_driver': '',
122 'use_dhcp': True,
123 'ip': '',
124 'netmask': '',
125 'gateway': '',
126 'domain': '',
127 'dns1': '',
128 'dns2': ''
131 # Combine essid and bssid to make a config file section name
133 #Parameters:
135 # 'essid' -- string - AP ESSID
137 # 'bssid' -- string - AP BSSID
139 #Returns:
141 # string -- the bssid concatenated to a colon, concatenated to the essid
142 def make_section_name( essid, bssid ):
143 return essid + ':' + bssid
145 # Split a config file section name into an essid and a bssid
147 #Parameters:
149 # 'section' -- string - Config file section name
151 #Returns:
153 # list -- the essid and bssid
154 def split_section_name( section ):
155 parts = re.split(':', section)
156 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
158 # Run commands through the shell
160 #Parameters:
162 # 'command' -- tuple - The command and arguments to run
164 #Returns:
166 # boolean -- True on success, otherwise, False
167 def shellcmd( command, environment = None ):
168 try:
169 env_tmp = os.environ
170 env_tmp.update(environment)
171 command = ' '.join(command)
172 return_code = call(command, shell=True, env=env_tmp)
173 if return_code >= 0:
174 return True
175 else:
176 print >>sys.stderr, "Child was terminated by signal", -return_code
177 except OSError, exception:
178 print >>sys.stderr, "Execution failed:", exception
179 return False
181 # Speak feedback message to user
183 #Parameters:
185 # 'words' -- string - Message to speak to user
187 #Returns:
189 # nothing
190 def say( words ):
191 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
192 words = words.replace( "\"", "\\\"" )
193 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
195 # Scan for a limited time and return AP names and bssid found.
196 # Access points we find will be put on the outgoing Queue, apQueue.
198 #Parameters:
200 # 'confFile' -- ConfigFile - Config file object
202 # 'apQueue' -- Queue - Queue on which to put AP profiles
204 # 'commandQueue' -- Queue - Queue from which to read commands
206 #Returns:
208 # nothing
209 def scanning_thread( confFile, apQueue, commandQueue ):
210 # Setup our essid pattern matcher
211 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
212 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abg]+)", re.I | re.M | re.S )
213 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
214 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
215 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
216 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
218 access_points = {}
219 command = "scan"
220 while True:
221 try:
222 command = commandQueue.get_nowait()
223 if __debug__: print "scanning_thread received command:", command
224 command_read = True
225 except Queue.Empty:
226 command_read = False
227 if command == "scan":
228 if __debug__: print "Beginning scan pass"
229 # Some cards need to have the interface up to scan
230 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
231 # call ifconfig command and wait for return
232 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
233 # update the signal strengths
234 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
235 #if __debug__:
236 #print "Current IP ", get_current_ip()
237 #print "Current ESSID ", get_current_essid()
238 #print "Current BSSID ", get_current_bssid()
239 # zero out the signal levels for all access points
240 for bssid in access_points:
241 access_points[bssid]['signal'] = 0
242 # split the scan data based on the address line
243 hits = scandata.split(' - ')
244 for hit in hits:
245 # set the defaults for profile template
246 profile = get_new_profile()
247 m = essid_pattern.search( hit )
248 if m:
249 # we found an essid
250 profile['essid'] = m.groups()[1]
251 m = bssid_pattern.search( hit ) # get BSSID from scan
252 if m: profile['bssid'] = m.groups()[1]
253 m = protocol_pattern.search( hit ) # get protocol from scan
254 if m: profile['protocol'] = m.groups()[1]
255 m = mode_pattern.search( hit ) # get mode from scan
256 if m: profile['mode'] = m.groups()[1]
257 m = channel_pattern.search( hit ) # get channel from scan
258 if m: profile['channel'] = m.groups()[1]
259 m = enckey_pattern.search( hit ) # get encryption key from scan
260 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
261 m = signal_pattern.search( hit ) # get signal strength from scan
262 if m: profile['signal'] = m.groups()[1]
263 access_points[ profile['bssid'] ] = profile
264 for bssid in access_points:
265 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
266 # Put all, now or previously, sensed access_points into apQueue
267 try:
268 apQueue.put_nowait( access_points[bssid] )
269 except Queue.Full:
270 pass
271 elif command == "exit":
272 if __debug__: print "Exiting scanning_thread"
273 return
274 if command_read: commandQueue.task_done()
275 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
276 sleep( 3 )
277 else:
278 sleep( 1 )
281 # Manage a connection; including reporting connection state,
282 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
283 class ConnectionManager():
284 # Create a new connection manager which can read a config file and send to scanning thread
285 # command Queue. A new manager checks for a pre-existing connection and takes
286 # its AP profile from the ESSID and BSSID to which it is currently attached.
288 #Parameters:
290 # 'confFile' -- ConfigFile - Config file object
292 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
294 #Returns:
296 # ConnectionManager instance
297 def __init__( self, confFile, commandQueue ):
298 self.confFile = confFile
299 self.commQueue = commandQueue
300 # is connection running?
301 self.state = False
302 if self.get_current_ip():
303 self.state = True
304 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
306 # Change the interface state: up or down.
308 #Parameters:
310 # 'state' -- string - The state to which to change the interface.
312 #Returns:
314 # nothing
315 def if_change( self, state ):
316 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
317 # call ifconfig command and wait for return
318 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
320 # Connect to the specified AP.
322 #Parameters:
324 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
326 # 'status_window' -- status_window - Instance of window to use to update user.
328 #Returns:
330 # nothing
331 def connect_to_network( self, profile, status_window ):
332 self.profile = profile
333 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
334 say( msg )
335 if __debug__: print " %s" % msg
336 # ready to dance
337 # Let's run the connection prescript
338 if self.profile['con_prescript'].strip() != '':
339 # got something to execute
340 # run connection prescript through shell and wait for return
341 if __debug__: print "executing connection prescript:", self.profile['con_prescript']
342 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
343 if status_window:
344 status_window.run()
345 # Some cards need to have the interface up
346 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
347 self.if_change('up')
348 # Start building iwconfig command line, command
349 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
350 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
351 # Setting essid
352 iwconfig_command.append( 'essid' )
353 iwconfig_command.append( self.profile['essid'] )
354 # Setting nick
355 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
356 # Setting key
357 iwconfig_command.append( 'key' )
358 if self.profile['key'] == '':
359 iwconfig_command.append( 'off' )
360 else:
361 iwconfig_command.append( self.profile['key'] )
362 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
363 # Setting mode to Master, this will cause association with the AP to fail
364 #iwconfig_command.append( 'mode' )
365 #iwconfig_command.append( 'Master' )
366 # Setting channel
367 if self.profile['channel'] != '':
368 iwconfig_command.append( 'channel' )
369 iwconfig_command.append( self.profile['channel'] )
370 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
371 iwconfig_command.append( 'ap' )
372 iwconfig_command.append( self.profile['bssid'] )
373 # Some cards require a commit
374 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
375 if __debug__: print 'iwconfig_args %s ' % ( iwconfig_args, )
376 iwconfig_command.append( 'commit' )
377 # call iwconfig command and wait for return
378 if not shellcmd(iwconfig_command): return
379 # Now normal network stuff
380 # Kill off any existing DHCP clients running
381 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
382 if __debug__: print "Killing existing DHCP..."
383 try:
384 if self.confFile.get_opt('DHCP.kill_args') != '':
385 # call DHCP client kill command and wait for return
386 if __debug__: print self.confFile.get_opt('DHCP.command') + " " + self.confFile.get_opt('DHCP.kill_args')
387 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
388 else:
389 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
390 except OSError:
391 print "failed to kill DHCP client"
392 sys.exit()
393 finally:
394 print "Stale pid file. Removing..."
395 os.remove(self.confFile.get_opt('DHCP.pidfile'))
396 # Kill off any existing WPA supplicants running
397 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
398 if __debug__: print "Killing existing WPA supplicant..."
399 try:
400 if not self.confFile.get_opt('WPA.kill_command') != '':
401 # call WPA supplicant kill command and wait for return
402 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
403 else:
404 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
405 except OSError:
406 print "failed to kill WPA supplicant"
407 sys.exit()
408 finally:
409 print "Stale pid file. Removing..."
410 os.remove(self.confFile.get_opt('WPA.pidfile'))
411 # Begin WPA supplicant
412 if self.profile['use_wpa'] :
413 if __debug__: print "WPA args: %s" % ( wpa_options, )
414 if status_window:
415 status_window.update_message("WPA supplicant starting")
416 # call WPA supplicant command and do not wait for return
417 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
418 if self.profile['use_dhcp'] :
419 if __debug__: print "Disable iwlist while dhcp in progress..."
420 try:
421 self.commQueue.put("pause")
422 except Queue.Full:
423 pass
424 if status_window:
425 status_window.update_message("Acquiring IP Address (DHCP)")
426 # call DHCP client command and do not wait for return
427 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
428 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
429 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
430 dhcp_proc = Popen(dhcp_command, stdout=None)
431 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
432 tick = 0.25
433 waiting = dhcp_proc.poll()
434 while waiting == None:
435 waiting = dhcp_proc.poll()
436 if timer < 0:
437 os.kill(dhcp_proc.pid, SIGTERM)
438 break
439 if sys.modules.has_key("gtk"):
440 while gtk.events_pending():
441 gtk.main_iteration(False)
442 timer -= tick
443 sleep(tick)
444 if not self.get_current_ip():
445 if status_window:
446 status_window.update_message("Could not get IP address!")
447 if sys.modules.has_key("gtk"):
448 while gtk.events_pending():
449 gtk.main_iteration(False)
450 sleep(3)
451 else:
452 print "Could not get IP address!"
453 self.disconnect_interface()
454 else:
455 if status_window:
456 status_window.update_message("Got IP address. Done.")
457 self.state = True
458 if sys.modules.has_key("gtk"):
459 while gtk.events_pending():
460 gtk.main_iteration(False)
461 sleep(2)
462 # Re-enable iwlist
463 try:
464 self.commQueue.put("scan")
465 except Queue.Full:
466 pass
467 else:
468 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'] )
469 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
470 resolv_contents = ''
471 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
472 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
473 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
474 if ( resolv_contents != '' ):
475 resolv_file=open('/etc/resolv.conf', 'w')
476 resolv_file.write(s)
477 resolv_file.close
478 if not shellcmd([ifconfig_command]): return
479 if not shellcmd([route_command]): return
480 self.state = True
481 # Let's run the connection postscript
482 con_postscript = self.profile['con_postscript']
483 if self.profile['con_postscript'].strip() != '':
484 if __debug__: print "executing connection postscript:", self.profile['con_postscript']
485 shellcmd([self.profile['con_postscript']], environment={"WIFIRADAR_IP": self.get_current_ip(), "WIFIRADAR_ESSID": self.get_current_essid(), "WIFIRADAR_BSSID": self.get_current_bssid(), "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
486 if status_window:
487 status_window.destroy()
489 # Disconnect from the AP with which a connection has been established/attempted.
491 #Parameters:
493 # nothing
495 #Returns:
497 # nothing
498 def disconnect_interface( self ):
499 msg = "Disconnecting"
500 say( msg )
501 if __debug__: print msg
502 # Pause scanning while manipulating card
503 try:
504 self.commQueue.put("pause")
505 except Queue.Full:
506 pass
507 if self.state:
508 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
509 # Let's run the disconnection prescript
510 if self.profile['dis_prescript'].strip() != '':
511 if __debug__: print "executing disconnection prescript:", self.profile['dis_prescript']
512 shellcmd([self.profile['dis_prescript']], environment={"WIFIRADAR_IP": self.get_current_ip(), "WIFIRADAR_ESSID": self.get_current_essid(), "WIFIRADAR_BSSID": self.get_current_bssid(), "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
513 if __debug__: print "Kill off any existing DHCP clients running..."
514 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
515 if __debug__: print "Killing existing DHCP..."
516 try:
517 if self.confFile.get_opt('DHCP.kill_args') != '':
518 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
519 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
520 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
521 if __debug__: print "DHCP command", dhcp_command
522 # call DHCP client command and wait for return
523 if not shellcmd(dhcp_command): return
524 else:
525 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
526 except OSError:
527 print "failed to kill DHCP client"
528 if __debug__: print "Kill off any existing WPA supplicants running..."
529 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
530 if __debug__: print "Killing existing WPA supplicant..."
531 try:
532 if not self.confFile.get_opt('WPA.kill_command') != '':
533 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
534 if not shellcmd(wpa_command): return
535 else:
536 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
537 except OSError:
538 print "failed to kill WPA supplicant"
539 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
540 if __debug__: print "Let's clear out the wireless stuff"
541 if __debug__: print 'Now take the interface down'
542 self.if_change('down')
543 if __debug__: print 'Since it may be brought back up by the next scan, lets unset its IP'
544 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
545 # Let's run the disconnection postscript
546 if self.profile['dis_postscript'].strip() != '':
547 if __debug__: print "executing disconnection postscript:", self.profile['dis_postscript']
548 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
549 self.state = False
550 if __debug__: print 'Disconnect complete.'
551 # Begin scanning again
552 try:
553 self.commQueue.put("scan")
554 except Queue.Full:
555 pass
557 # Returns the current IP, if any, by calling ifconfig.
559 #Parameters:
561 # nothing
563 #Returns:
565 # string or None -- the IP address or None (if no there is no current connection)
566 def get_current_ip( self ):
567 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
568 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
569 # Be careful to the language (inet adr: in French for example)
571 # Hi Brian
573 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
574 # There the string in ifconfig is inet Adresse for the IP which isn't
575 # found by the current get_current_ip function in wifi-radar. I changed
576 # the according line (#289; gentoo, v1.9.6-r1) to
577 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
578 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
580 # I'd be happy if you could incorporate this small change because as now
581 # I've got to change the file every time it is updated.
583 # Best wishes
585 # Simon
586 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
587 line = ifconfig_info.read()
588 if ip_re.search( line ):
589 return ip_re.search( line ).group(1)
590 return None
592 # Returns the current ESSID, if any, by calling iwconfig.
594 #Parameters:
596 # nothing
598 #Returns:
600 # string or None -- the ESSID or None (if no there is no current association)
601 def get_current_essid( self ):
602 """Returns the current ESSID if any by calling iwconfig"""
603 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
604 # Be careful to the language (inet adr: in French for example)
605 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
606 line = iwconfig_info.read()
607 if essid_re.search( line ):
608 return essid_re.search( line ).group(2)
609 return None
611 # Returns the current BSSID, if any, by calling iwconfig.
613 #Parameters:
615 # nothing
617 #Returns:
619 # string or None -- the BSSID or None (if no there is no current association)
620 def get_current_bssid( self ):
621 """Returns the current BSSID if any by calling iwconfig"""
622 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
623 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
624 line = iwconfig_info.read()
625 if bssid_re.search( line ):
626 return bssid_re.search( line ).group(2)
627 return None
631 # The main user interface window for WiFi Radar. This class also is the control
632 # center for most of the rest of the operations.
633 class radar_window:
634 # Create a new radar_window.
636 #Parameters:
638 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
640 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
642 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
644 #Returns:
646 # radar_window instance
647 def __init__( self, confFile, apQueue, commQueue ):
648 global signal_xpm_none
649 global signal_xpm_low
650 global signal_xpm_barely
651 global signal_xpm_ok
652 global signal_xpm_best
653 global known_profile_icon
654 global unknown_profile_icon
655 global wifi_radar_icon
657 self.confFile = confFile
658 self.apQueue = apQueue
659 self.commandQueue = commQueue
660 self.access_points = {}
661 self.connection = None
663 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
664 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
665 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
666 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
667 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
668 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
669 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
670 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
671 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
672 self.window.set_icon( icon )
673 self.window.set_border_width( 10 )
674 self.window.set_size_request( 550, 300 )
675 self.window.set_title( "WiFi Radar" )
676 self.window.connect( 'delete_event', self.delete_event )
677 self.window.connect( 'destroy', self.destroy )
678 # let's create all our widgets
679 self.current_network = gtk.Label()
680 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
681 self.current_network.show()
682 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
683 self.close_button.show()
684 self.close_button.connect( 'clicked', self.delete_event, None )
685 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
686 self.about_button.show()
687 self.about_button.connect( 'clicked', self.show_about_info, None )
688 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
689 self.preferences_button.show()
690 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
691 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
692 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
693 self.plist = gtk.TreeView( self.pstore )
694 # The icons column, known and encryption
695 self.pix_cell = gtk.CellRendererPixbuf()
696 self.wep_cell = gtk.CellRendererPixbuf()
697 self.icons_cell = gtk.CellRendererText()
698 self.icons_col = gtk.TreeViewColumn()
699 self.icons_col.pack_start( self.pix_cell, False )
700 self.icons_col.pack_start( self.wep_cell, False )
701 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
702 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
703 self.plist.append_column( self.icons_col )
704 # The AP column
705 self.ap_cell = gtk.CellRendererText()
706 self.ap_col = gtk.TreeViewColumn( "Access Point" )
707 self.ap_col.pack_start( self.ap_cell, True )
708 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
709 self.plist.append_column( self.ap_col )
710 # The signal column
711 self.sig_cell = gtk.CellRendererPixbuf()
712 self.signal_col = gtk.TreeViewColumn( "Signal" )
713 self.signal_col.pack_start( self.sig_cell, True )
714 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
715 self.plist.append_column( self.signal_col )
716 # The mode column
717 self.mode_cell = gtk.CellRendererText()
718 self.mode_col = gtk.TreeViewColumn( "Mode" )
719 self.mode_col.pack_start( self.mode_cell, True )
720 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
721 self.plist.append_column( self.mode_col )
722 # The protocol column
723 self.prot_cell = gtk.CellRendererText()
724 self.protocol_col = gtk.TreeViewColumn( "802.11" )
725 self.protocol_col.pack_start( self.prot_cell, True )
726 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
727 self.plist.append_column( self.protocol_col )
728 # The channel column
729 self.channel_cell = gtk.CellRendererText()
730 self.channel_col = gtk.TreeViewColumn( "Channel" )
731 self.channel_col.pack_start( self.channel_cell, True )
732 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
733 self.plist.append_column( self.channel_col )
734 # DnD Ordering
735 self.plist.set_reorderable( True )
736 self.pstore.connect( 'row-changed', self.update_auto_profile_order )
737 # enable/disable buttons based on the selected network
738 self.selected_network = self.plist.get_selection()
739 self.selected_network.connect( 'changed', self.on_network_selection, None )
740 # the list scroll bar
741 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
742 sb.show()
743 self.plist.show()
744 # Add New button
745 self.new_button = gtk.Button( "_New" )
746 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
747 self.new_button.show()
748 # Add Configure button
749 self.edit_button = gtk.Button( "C_onfigure" )
750 self.edit_button.connect( 'clicked', self.edit_profile, None )
751 self.edit_button.show()
752 self.edit_button.set_sensitive(False)
753 # Add Delete button
754 self.delete_button = gtk.Button( "_Delete" )
755 self.delete_button.connect( 'clicked', self.delete_profile, None )
756 self.delete_button.show()
757 self.delete_button.set_sensitive(False)
758 # Add Connect button
759 self.connect_button = gtk.Button( "Co_nnect" )
760 self.connect_button.connect( 'clicked', self.connect_profile, None )
761 # Add Disconnect button
762 self.disconnect_button = gtk.Button( "D_isconnect" )
763 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
764 # lets add our widgets
765 rows = gtk.VBox( False, 3 )
766 net_list = gtk.HBox( False, 0 )
767 listcols = gtk.HBox( False, 0 )
768 prows = gtk.VBox( False, 0 )
769 # lets start packing
770 # the network list
771 net_list.pack_start( self.plist, True, True, 0 )
772 net_list.pack_start( sb, False, False, 0 )
773 # the rows level
774 rows.pack_start( net_list , True, True, 0 )
775 rows.pack_start( self.current_network, False, True, 0 )
776 # the list columns
777 listcols.pack_start( rows, True, True, 0 )
778 listcols.pack_start( prows, False, False, 5 )
779 # the list buttons
780 prows.pack_start( self.new_button, False, False, 2 )
781 prows.pack_start( self.edit_button, False, False, 2 )
782 prows.pack_start( self.delete_button, False, False, 2 )
783 prows.pack_end( self.connect_button, False, False, 2 )
784 prows.pack_end( self.disconnect_button, False, False, 2 )
786 self.window.action_area.pack_start( self.about_button )
787 self.window.action_area.pack_start( self.preferences_button )
788 self.window.action_area.pack_start( self.close_button )
790 rows.show()
791 prows.show()
792 listcols.show()
793 self.window.vbox.add( listcols )
794 self.window.vbox.set_spacing( 3 )
795 self.window.show_all()
797 # Now, immediately hide these two. The proper one will be
798 # displayed later, based on interface state. -BEF-
799 self.disconnect_button.hide()
800 self.connect_button.hide()
801 self.connect_button.set_sensitive(False)
803 # set up connection manager for later use
804 self.connection = ConnectionManager( self.confFile, self.commandQueue )
806 # Add our known profiles in order
807 for ap in self.confFile.auto_profile_order:
808 ap = ap.strip()
809 self.access_points[ ap ] = self.confFile.get_profile( ap )
810 wep = None
811 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
812 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'] ] )
813 # This is the first run (or, at least, no config file was present), so pop up the preferences window
814 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
815 self.confFile.remove_option('DEFAULT', 'new_file')
816 self.edit_preferences(self.preferences_button)
818 # Begin running radar_window in Gtk event loop.
820 #Parameters:
822 # nothing
824 #Returns:
826 # nothing
827 def main( self ):
828 gtk.main()
830 # Write the config file to disk and quit application.
832 #Parameters:
834 # 'widget' -- gtk.Widget - The widget sending the event.
836 #Returns:
838 # nothing
839 def destroy( self, widget = None):
840 self.confFile.write()
841 gtk.main_quit()
843 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
845 #Parameters:
847 # 'widget' -- gtk.Widget - The widget sending the event.
849 # 'data' -- tuple - list of arbitrary arguments (not used)
851 #Returns:
853 # boolean -- always return False (i.e. do not propigate the signal which called)
854 def delete_event( self, widget, data = None ):
855 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
856 try:
857 self.commandQueue.put("exit", True)
858 except Queue.Full:
859 pass
860 # Save the preferred networks order
861 self.update_auto_profile_order()
862 self.destroy()
863 return False
865 # Updates the on-screen profiles list.
867 #Parameters:
869 # nothing
871 #Returns:
873 # boolean -- always return True
874 def update_plist_items( self ):
875 # Indicate to PyGtk that only one Gtk thread should run here
876 gtk.gdk.threads_enter()
877 # update the current ip and essid
878 # set the state of connect/disconnect buttons based on whether we have an IP address
879 if self.connection:
880 if self.connection.state:
881 self.current_network.set_text( "Connected to %s\nip %s" % ( make_section_name( self.connection.get_current_essid(), self.connection.get_current_bssid() ), self.connection.get_current_ip() ) )
882 self.connect_button.hide()
883 self.disconnect_button.show()
884 else:
885 self.current_network.set_text( "Not Connected." )
886 self.disconnect_button.hide()
887 self.connect_button.show()
889 while True:
890 # Get profiles scanned by iwlist
891 try:
892 profile = self.apQueue.get_nowait()
893 except Queue.Empty:
894 break
895 else:
896 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
897 wep = None
898 if prow_iter != None:
899 # the AP is in the list of APs on the screen
900 apname = make_section_name(profile['essid'], profile['bssid'])
901 if self.access_points.has_key(apname):
902 # This AP has been configured and is/should be stored in the config file
903 profile['known'] = self.access_points[apname]['known']
904 self.access_points[apname]['available'] = profile['available']
905 self.access_points[apname]['encrypted'] = profile['encrypted']
906 self.access_points[apname]['signal'] = profile['signal']
907 self.access_points[apname]['mode'] = profile['mode']
908 self.access_points[apname]['protocol'] = profile['protocol']
909 self.access_points[apname]['channel'] = profile['channel']
910 # Set the 'known' values; False is default, overridden to True by self.access_points
911 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
912 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
913 self.pstore.set_value(prow_iter, 3, profile['available'])
914 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
915 self.pstore.set_value(prow_iter, 4, wep)
916 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
917 self.pstore.set_value(prow_iter, 6, profile['mode'])
918 self.pstore.set_value(prow_iter, 7, profile['protocol'])
919 self.pstore.set_value(prow_iter, 8, profile['channel'])
920 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
921 #for val in self.pstore[prow_iter]:
922 #print val,
923 else:
924 # the AP is not in the list of APs on the screen
925 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'] ] )
926 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
927 # Allow other Gtk threads to run
928 gtk.gdk.threads_leave()
929 #print "update_plist_items: Empty apQueue"
930 return True
932 # Return the proper icon for a value of known.
934 #Parameters:
936 # 'known' -- boolean - Whether the AP is known (i.e. configured)
938 #Returns:
940 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
941 def pixbuf_from_known( self, known ):
942 """ return the proper icon for value of known """
943 if known:
944 return self.known_profile_icon
945 else:
946 return self.unknown_profile_icon
948 # Return an icon indicating the signal level.
950 #Parameters:
952 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
954 #Returns:
956 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
957 def pixbuf_from_signal( self, signal ):
958 signal = int( signal )
959 #print "signal level:", signal
960 if signal < 3:
961 return self.signal_none_pb
962 elif signal < 12:
963 return self.signal_low_pb
964 elif signal < 20:
965 return self.signal_barely_pb
966 elif signal < 35:
967 return self.signal_ok_pb
968 elif signal >= 35:
969 return self.signal_best_pb
970 else:
971 return None
973 # Return row which holds specified ESSID and BSSID.
975 #Parameters:
977 # 'essid' -- string - ESSID to match
979 # 'bssid' -- string - BSSID to match
981 #Returns:
983 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
984 def get_row_by_ap( self, essid, bssid ):
985 for row in self.pstore:
986 if ( row[0] == essid + "\n" + bssid ):
987 #print "matched:", row.iter, essid, bssid
988 return row.iter
989 return None
991 # Enable/disable buttons based on the selected network.
993 #Parameters:
995 # 'widget' -- gtk.Widget - The widget sending the event.
997 # 'data' -- tuple - list of arbitrary arguments (not used)
999 #Returns:
1001 # nothing
1002 def on_network_selection( self, widget, data = None ):
1003 ( store, selected_iter ) = self.selected_network.get_selected()
1004 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1005 # if no networks are selected, disable all buttons except New
1006 # (this occurs after a drag-and-drop)
1007 if selected_iter == None:
1008 self.edit_button.set_sensitive(False)
1009 self.delete_button.set_sensitive(False)
1010 self.connect_button.set_sensitive(False)
1011 return
1012 # enable/disable buttons
1013 self.connect_button.set_sensitive(True)
1014 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1015 self.edit_button.set_sensitive(True)
1016 self.delete_button.set_sensitive(True)
1017 else:
1018 self.edit_button.set_sensitive(True)
1019 self.delete_button.set_sensitive(False)
1021 # Init and run the about dialog
1023 #Parameters:
1025 # 'widget' -- gtk.Widget - The widget sending the event.
1027 # 'data' -- tuple - list of arbitrary arguments (not used)
1029 #Returns:
1031 # nothing
1032 def show_about_info( self, widget, data=None ):
1033 about = about_dialog()
1034 about.run()
1035 about.destroy()
1037 # Init and run the preferences dialog
1039 #Parameters:
1041 # 'widget' -- gtk.Widget - The widget sending the event.
1043 # 'data' -- tuple - list of arbitrary arguments (not used)
1045 #Returns:
1047 # nothing
1048 def edit_preferences( self, widget, data=None ):
1049 prefs = preferences_dialog( self, self.confFile )
1050 response = prefs.run()
1051 prefs.destroy()
1052 if response == int(gtk.RESPONSE_ACCEPT):
1053 prefs.save()
1055 # Respond to a request to create a new AP profile
1057 #Parameters:
1059 # 'widget' -- gtk.Widget - The widget sending the event.
1061 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1063 # 'data' -- tuple - list of arbitrary arguments (not used)
1065 #Returns:
1067 # boolean -- True if a profile was created and False if profile creation was canceled.
1068 def create_new_profile( self, widget, profile, data=None ):
1069 profile_editor = profile_dialog( self, profile )
1070 profile = profile_editor.run()
1071 profile_editor.destroy()
1072 if profile:
1073 apname = make_section_name( profile['essid'], profile['bssid'] )
1074 # Check that the ap does not exist already
1075 if apname in self.confFile.profiles():
1076 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) )
1077 dlg.run()
1078 dlg.destroy()
1079 del dlg
1080 # try again
1081 self.access_points[ apname ] = profile
1082 self.confFile.set_section( apname, profile )
1083 # if it is not in the auto_profile_order add it
1084 if apname not in self.confFile.auto_profile_order:
1085 self.confFile.auto_profile_order.append(apname)
1086 # add to the store
1087 wep = None
1088 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1089 return True
1090 else:
1091 # Did not create new profile
1092 return False
1094 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1095 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1097 #Parameters:
1099 # 'widget' -- gtk.Widget - The widget sending the event.
1101 # 'data' -- tuple - list of arbitrary arguments (not used)
1103 #Returns:
1105 # nothing
1106 def edit_profile( self, widget, data=None ):
1107 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1108 if not selected_iter: return
1109 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1110 apname = make_section_name( row_start[0], row_start[1] )
1111 profile = self.confFile.get_profile( apname )
1112 if profile:
1113 profile_editor = profile_dialog( self, profile )
1114 profile = profile_editor.run()
1115 profile_editor.destroy()
1116 if profile:
1117 if __debug__:
1118 print "Got edited profile ", profile
1119 apname = make_section_name( profile['essid'], profile['bssid'] )
1120 self.access_points[ apname ] = profile
1121 self.confFile.set_section( apname, profile )
1122 else:
1123 profile = get_new_profile()
1124 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1125 self.create_new_profile( widget, profile, data )
1127 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1129 #Parameters:
1131 # 'widget' -- gtk.Widget - The widget sending the event.
1133 # 'data' -- tuple - list of arbitrary arguments (not used)
1135 #Returns:
1137 # nothing
1138 def delete_profile( self, widget, data=None ):
1139 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1140 if not selected_iter: return
1141 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1142 known = store.get_value( selected_iter, 1 )
1143 if not known: return
1144 dlg = gtk.MessageDialog(
1145 self.window,
1146 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1147 gtk.MESSAGE_QUESTION,
1148 gtk.BUTTONS_YES_NO,
1149 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1150 res = dlg.run()
1151 dlg.destroy()
1152 del dlg
1153 if res == gtk.RESPONSE_NO: return
1154 # Remove it
1155 apname = make_section_name( essid, bssid )
1156 del self.access_points[ apname ]
1157 self.confFile.remove_section( apname )
1158 print "delete_profile: ", apname, ":", self.confFile.auto_profile_order
1159 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1160 self.pstore.remove( selected_iter )
1161 # Let's save our current state
1162 self.update_auto_profile_order()
1164 # Respond to a request to connect to an AP.
1166 #Parameters:
1168 # 'widget' -- gtk.Widget - The widget sending the event.
1170 # 'profile' -- dictionary - The AP profile to which to connect.
1172 # 'data' -- tuple - list of arbitrary arguments (not used)
1174 #Returns:
1176 # nothing
1177 def connect_profile( self, widget, profile, data=None ):
1178 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1179 if not selected_iter: return
1180 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1181 known = store.get_value( selected_iter, 2 )
1182 if not known:
1183 if data != 'noconnect':
1184 dlg = gtk.MessageDialog(
1185 self.window,
1186 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1187 gtk.MESSAGE_QUESTION,
1188 gtk.BUTTONS_YES_NO,
1189 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1190 res = dlg.run()
1191 dlg.destroy()
1192 del dlg
1193 if res == gtk.RESPONSE_NO: return
1194 profile = get_new_profile()
1195 profile['essid'] = essid
1196 profile['bssid'] = bssid
1197 if not self.create_new_profile( widget, profile, data ):
1198 return
1199 apname = make_section_name( essid, bssid )
1200 self.connection.connect_to_network( self.access_points[apname], status_window( self ) )
1202 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1204 #Parameters:
1206 # 'widget' -- gtk.Widget - The widget sending the event.
1208 # 'data' -- tuple - list of arbitrary arguments (not used)
1210 #Returns:
1212 # nothing
1213 def disconnect_profile( self, widget, data=None ):
1214 self.connection.disconnect_interface()
1216 # Update the config file auto profile order from the on-screen order
1218 #Parameters:
1220 # 'widget' -- gtk.Widget - The widget sending the event.
1222 # 'data' -- tuple - list of arbitrary arguments (not used)
1224 # 'data2' -- tuple - list of arbitrary arguments (not used)
1226 #Returns:
1228 # nothing
1229 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1230 # recreate the auto_profile_order
1231 auto_profile_order = []
1232 piter = self.pstore.get_iter_first()
1233 while piter:
1234 # only if it's known
1235 if self.pstore.get_value( piter, 2 ) == True:
1236 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1237 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1238 piter = self.pstore.iter_next( piter )
1239 self.confFile.auto_profile_order = auto_profile_order
1242 # Button to allow user to choose a file and put value into specified gtk.Entry
1243 class file_browse_button(gtk.Button):
1244 # Create a button to simulate a File/Open
1246 #Parameters:
1248 # 'parent' -- gtk.Object -- Usually, the calling window.
1250 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1252 #Returns:
1254 # file_browse_button instance
1255 def __init__( self, parent, entry ):
1256 self.parent_window = parent
1257 self.entry = entry
1258 gtk.Button.__init__(self, "Browse", None)
1259 #self.
1260 self.browser_dialog = gtk.FileChooserDialog(None, self.parent_window, gtk.FILE_CHOOSER_ACTION_OPEN, ( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK ), None)
1261 self.connect("clicked", self.browse_files)
1263 # Show filechooser dialog and get user selection
1265 #Parameters:
1267 # 'widget' -- gtk.Widget -- The widget sending the event.
1269 #Returns:
1271 # nothing
1273 #NOTES:
1275 # updates entry value
1277 def browse_files( self, widget ):
1278 self.browser_dialog.set_filename(self.entry.get_text())
1279 self.browser_dialog.run()
1280 self.entry.set_text(self.browser_dialog.get_filename())
1281 self.browser_dialog.destroy()
1284 # The preferences dialog. Edits some items in the DEFAULT section of the config file.
1285 class preferences_dialog:
1286 # Create a new preferences_dialog.
1288 #Parameters:
1290 # 'parent' -- gtk.Object - Usually, the calling window.
1292 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1294 #Returns:
1296 # preferences_dialog instance
1297 def __init__( self, parent, confFile ):
1298 global wifi_radar_icon
1299 self.parent = parent
1300 self.confFile = confFile
1301 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1302 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1303 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1304 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1305 self.dialog.set_icon( icon )
1306 self.dialog.set_resizable( True )
1307 self.dialog.set_transient_for( self.parent.window )
1308 self.tooltips = gtk.Tooltips()
1310 # set up preferences widgets
1313 # auto detect wireless device
1314 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1316 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1318 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1319 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1320 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1321 self.dialog.vbox.pack_start(self.w_auto_detect, False, False, 5)
1323 # network interface selecter
1324 self.w_interface = gtk.combo_box_entry_new_text()
1325 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1326 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1327 for device in wireless_devices:
1328 if device != self.confFile.get_opt('DEFAULT.interface'):
1329 self.w_interface.append_text(device)
1330 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1331 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1332 self.w_interface.set_active(0)
1333 self.w_interface_label = gtk.Label("Wireless device")
1334 self.w_hbox1 = gtk.HBox(False, 0)
1335 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1336 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1337 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1338 self.dialog.vbox.pack_start(self.w_hbox1, False, False, 5)
1340 # scan timeout (spin button of integers from 1 to 100)
1341 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,1 )
1342 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1343 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1344 self.w_scan_timeout.set_numeric(True)
1345 self.w_scan_timeout.set_snap_to_ticks(True)
1346 self.w_scan_timeout.set_wrap(False)
1347 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1348 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1349 self.w_hbox2 = gtk.HBox(False, 0)
1350 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1351 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1352 self.dialog.vbox.pack_start(self.w_hbox2, False, False, 5)
1354 # speak up
1355 self.w_speak_up = gtk.CheckButton("Use speak-up")
1356 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1357 self.w_speak_up.connect("toggled", self.toggle_speak)
1358 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1359 self.dialog.vbox.pack_start(self.w_speak_up, False, False, 5)
1361 # speak up command
1362 self.w_speak_cmd = gtk.Entry()
1363 self.w_speak_cmd.set_width_chars(16)
1364 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1365 self.w_speak_cmd_label = gtk.Label("Speak Command")
1366 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1367 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1368 self.w_hbox3 = gtk.HBox(False, 0)
1369 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1370 self.w_hbox3.pack_start(self.w_speak_cmd_label, False, False, 5)
1371 self.w_hbox3.pack_start(self.w_speak_cmd, True, True, 0)
1372 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1373 self.dialog.vbox.pack_start(self.w_hbox3, False, False, 5)
1375 # commit required
1376 self.w_commit_required = gtk.CheckButton("Commit required")
1377 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1378 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1379 self.dialog.vbox.pack_start(self.w_commit_required, False, False, 5)
1381 # ifup required
1382 self.w_ifup_required = gtk.CheckButton("Ifup required")
1383 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1384 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1385 self.dialog.vbox.pack_start(self.w_ifup_required, False, False, 5)
1387 # ifconfig command
1388 self.w_ifconfig_cmd = gtk.Entry()
1389 self.w_ifconfig_cmd.set_width_chars(16)
1390 self.w_ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1391 self.w_ifconfig_cmd_button = file_browse_button(self.dialog, self.w_ifconfig_cmd)
1392 self.w_hbox4.pack_start(self.w_ifconfig_cmd_button, False, False, 0)
1393 self.w_ifconfig_cmd_label = gtk.Label("ifconfig Command")
1394 self.w_hbox4 = gtk.HBox(True, 0)
1395 self.w_hbox4.pack_start(self.w_ifconfig_cmd_label, False, False, 5)
1396 self.w_hbox4.pack_start(self.w_ifconfig_cmd, True, True, 0)
1397 self.dialog.vbox.pack_start(self.w_hbox4, False, False, 5)
1399 # iwconfig command
1400 self.w_iwconfig_cmd = gtk.Entry()
1401 self.w_iwconfig_cmd.set_width_chars(16)
1402 self.w_iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1403 self.w_iwconfig_cmd_button = file_browse_button(self.dialog, self.w_iwconfig_cmd)
1404 self.w_hbox5.pack_start(self.w_iwconfig_cmd_button, False, False, 0)
1405 self.w_iwconfig_cmd_label = gtk.Label("iwconfig Command")
1406 self.w_hbox5 = gtk.HBox(True, 0)
1407 self.w_hbox5.pack_start(self.w_iwconfig_cmd_label, False, False, 5)
1408 self.w_hbox5.pack_start(self.w_iwconfig_cmd, True, True, 0)
1409 self.dialog.vbox.pack_start(self.w_hbox5, False, False, 5)
1411 # iwlist command
1412 self.w_iwlist_cmd = gtk.Entry()
1413 self.w_iwlist_cmd.set_width_chars(16)
1414 self.w_iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1415 self.w_iwlist_cmd_button = file_browse_button(self.dialog, self.w_iwlist_cmd)
1416 self.w_hbox6.pack_start(self.w_iwlist_cmd_button, False, False, 0)
1417 self.w_iwlist_cmd_label = gtk.Label("iwlist Command")
1418 self.w_hbox6 = gtk.HBox(True, 0)
1419 self.w_hbox6.pack_start(self.w_iwlist_cmd_label, False, False, 5)
1420 self.w_hbox6.pack_start(self.w_iwlist_cmd, True, True, 0)
1421 self.dialog.vbox.pack_start(self.w_hbox6, False, False, 5)
1423 # route command
1424 self.w_route_cmd = gtk.Entry()
1425 self.w_route_cmd.set_width_chars(16)
1426 self.w_route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1427 self.w_route_cmd_button = file_browse_button(self.dialog, self.w_route_cmd)
1428 self.w_hbox7.pack_start(self.w_route_cmd_button, False, False, 0)
1429 self.w_route_cmd_label = gtk.Label("route Command")
1430 self.w_hbox7 = gtk.HBox(True, 0)
1431 self.w_hbox7.pack_start(self.w_route_cmd_label, False, False, 5)
1432 self.w_hbox7.pack_start(self.w_route_cmd, True, True, 0)
1433 self.dialog.vbox.pack_start(self.w_hbox7, False, False, 5)
1436 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1438 #Parameters:
1440 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1442 # 'data' -- tuple - list of arbitrary arguments (not used)
1444 #Returns:
1446 # nothing
1447 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1448 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1450 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1452 #Parameters:
1454 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1456 # 'data' -- tuple - list of arbitrary arguments (not used)
1458 #Returns:
1460 # nothing
1461 def toggle_speak(self, speak_toggle, data=None):
1462 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1463 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1465 # Display preferences dialog and operate until canceled or okayed.
1467 #Parameters:
1469 # nothing
1471 #Returns:
1473 # integer -- gtk response ID
1474 def run(self):
1475 self.dialog.show_all()
1476 return self.dialog.run()
1478 # Write updated values to config file.
1480 #Parameters:
1482 # nothing
1484 #Returns:
1486 # nothing
1487 def save(self):
1488 print "saving prefs"
1489 if self.w_auto_detect.get_active():
1490 set_network_device("auto_detect")
1491 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1492 else:
1493 set_network_device(self.w_interface.child.get_text())
1494 self.confFile.set_opt('DEFAULT.interface', self.w_interface.child.get_text())
1495 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1496 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1497 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1498 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1499 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
1500 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'up'])
1501 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1502 self.confFile.set_opt('DEFAULT.ifconfig_command', self.w_ifconfig_cmd.get_text())
1503 self.confFile.set_opt('DEFAULT.iwconfig_command', self.w_iwconfig_cmd.get_text())
1504 self.confFile.set_opt('DEFAULT.iwlist_command', self.w_iwlist_cmd.get_text())
1505 self.confFile.set_opt('DEFAULT.route_command', self.w_route_cmd.get_text())
1507 # Remove preferences window.
1509 #Parameters:
1511 # nothing
1513 #Returns:
1515 # nothing
1516 def destroy(self):
1517 self.dialog.destroy()
1518 del self.dialog
1521 # Edit and return an AP profile.
1522 class profile_dialog:
1523 # Create a new profile_dialog.
1525 #Parameters:
1527 # 'parent' -- gtk.Object - Usually, the calling window.
1529 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1531 #Returns:
1533 # profile_dialog instance
1534 def __init__( self, parent, profile ):
1535 global wifi_radar_icon
1536 self.parent = parent
1537 self.profile = profile
1538 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1539 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1540 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1541 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1542 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1543 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1544 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1545 self.dialog.set_icon( icon )
1546 self.dialog.set_resizable( False )
1547 self.dialog.set_transient_for( self.parent.window )
1548 #self.dialog.set_size_request( 400, 400 )
1549 #################
1550 essid_table = gtk.Table( 1, 2, False )
1551 essid_table.set_row_spacings( 3 )
1552 essid_table.set_col_spacings( 3 )
1554 # The essid labels
1555 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1556 # The essid textboxes
1557 self.essid_entry = gtk.Entry( 32 )
1558 self.essid_entry.set_text( self.profile['essid'] )
1559 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1560 # Add the essid table to the dialog
1561 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1563 bssid_table = gtk.Table( 1, 2, False )
1564 bssid_table.set_row_spacings( 3 )
1565 bssid_table.set_col_spacings( 3 )
1566 # The bssid labels
1567 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1568 # The bssid textboxes
1569 self.bssid_entry = gtk.Entry( 32 )
1570 self.bssid_entry.set_text( self.profile['bssid'] )
1571 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1572 #self.key = gtk.Entry( 32 )
1573 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1574 # Add the bssid table to the dialog
1575 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1577 # create the wifi expander
1578 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1579 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1580 wifi_table = gtk.Table( 4, 2, False )
1581 wifi_table.set_row_spacings( 3 )
1582 wifi_table.set_col_spacings( 3 )
1583 # The Wifi labels
1584 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1585 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1586 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1587 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1588 # The Wifi text boxes
1589 self.mode_combo = gtk.combo_box_new_text()
1590 for mode in self.WIFI_MODES:
1591 self.mode_combo.append_text( mode )
1592 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1593 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1594 self.channel_combo = gtk.combo_box_new_text()
1595 for channel in self.WIFI_CHANNELS:
1596 self.channel_combo.append_text( channel )
1597 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1598 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1600 self.key_entry = gtk.Entry( 64 )
1601 self.key_entry.set_text( self.profile['key'] )
1602 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1604 self.security_combo = gtk.combo_box_new_text()
1605 for security in self.WIFI_SECURITY:
1606 self.security_combo.append_text( security )
1607 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1608 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1609 # Add the wifi table to the expander
1610 self.wifi_expander.add( wifi_table )
1611 # Add the expander to the dialog
1612 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1614 # create the wpa expander
1615 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1616 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1617 wpa_table = gtk.Table( 1, 2, False )
1618 wpa_table.set_row_spacings( 3 )
1619 wpa_table.set_col_spacings( 3 )
1620 # The labels
1621 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1622 # The text boxes
1623 self.wpa_driver_entry = gtk.Entry()
1624 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1625 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1626 # Add the wpa table to the expander
1627 self.wpa_expander.add( wpa_table )
1628 # Add the expander to the dialog
1629 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1631 # create the dhcp expander
1632 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1633 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1634 ip_table = gtk.Table( 6, 2, False )
1635 ip_table.set_row_spacings( 3 )
1636 ip_table.set_col_spacings( 3 )
1637 # The IP labels
1638 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1639 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1640 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1641 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1642 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1643 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1644 # The IP text boxes
1645 self.ip_entry = gtk.Entry( 15 )
1646 self.ip_entry.set_text( self.profile['ip'] )
1647 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1648 self.netmask_entry = gtk.Entry( 15 )
1649 self.netmask_entry.set_text( self.profile['netmask'] )
1650 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1651 self.gw_entry = gtk.Entry( 15 )
1652 self.gw_entry.set_text( self.profile['gateway'] )
1653 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1654 self.domain_entry = gtk.Entry( 32 )
1655 self.domain_entry.set_text( self.profile['domain'] )
1656 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1657 self.dns1_entry = gtk.Entry( 15 )
1658 self.dns1_entry.set_text( self.profile['dns1'] )
1659 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1660 self.dns2_entry = gtk.Entry( 15 )
1661 self.dns2_entry.set_text( self.profile['dns2'] )
1662 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1663 # Add the ip table to the expander
1664 self.dhcp_expander.add( ip_table )
1665 # Add the expander to the dialog
1666 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1668 # create the connection-building postpre expander
1669 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1670 con_pp_table = gtk.Table( 2, 2, False )
1671 con_pp_table.set_row_spacings( 3 )
1672 con_pp_table.set_col_spacings( 3 )
1673 # The labels
1674 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1675 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1676 # The text boxes
1677 self.con_prescript_entry = gtk.Entry()
1678 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1679 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1680 self.con_postscript_entry = gtk.Entry()
1681 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1682 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1683 # Add the pp table to the expander
1684 self.con_pp_expander.add( con_pp_table )
1685 # Add the expander to the dialog
1686 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1688 # create the disconnection postpre expander
1689 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1690 dis_pp_table = gtk.Table( 2, 2, False )
1691 dis_pp_table.set_row_spacings( 3 )
1692 dis_pp_table.set_col_spacings( 3 )
1693 # The labels
1694 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1695 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1696 # The text boxes
1697 self.dis_prescript_entry = gtk.Entry()
1698 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1699 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1700 self.dis_postscript_entry = gtk.Entry()
1701 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1702 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1703 # Add the pp table to the expander
1704 self.dis_pp_expander.add( dis_pp_table )
1705 # Add the expander to the dialog
1706 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1708 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1710 #Parameters:
1712 # nothing
1714 #Returns:
1716 # dictionary or None -- a profile, or None on cancel
1717 def run( self ):
1718 self.dialog.show_all()
1719 if self.dialog.run():
1720 self.profile['known'] = True
1721 self.profile['essid'] = self.essid_entry.get_text().strip()
1722 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1723 self.profile['key'] = self.key_entry.get_text().strip()
1724 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1725 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1726 self.profile['encrypted'] = ( self.profile['security'] != '' )
1727 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1728 self.profile['protocol'] = 'g'
1729 self.profile['available'] = ( self.profile['signal'] > 0 )
1730 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1731 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1732 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1733 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1734 # wpa
1735 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1736 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1737 # dhcp
1738 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1739 self.profile['ip'] = self.ip_entry.get_text().strip()
1740 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1741 self.profile['gateway'] = self.gw_entry.get_text().strip()
1742 self.profile['domain'] = self.domain_entry.get_text().strip()
1743 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1744 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1745 return self.profile
1746 return None
1748 # Remove profile dialog.
1750 #Parameters:
1752 # nothing
1754 #Returns:
1756 # nothing
1757 def destroy( self ):
1758 self.dialog.destroy()
1759 del self.dialog
1761 # Respond to expanding/hiding IP segment.
1763 #Parameters:
1765 # 'widget' -- gtk.Widget - The widget sending the event.
1767 # 'data' -- tuple - List of arbitrary arguments (not used)
1769 #Returns:
1771 # nothing
1772 def toggle_use_dhcp( self, widget, data = None ):
1773 expanded = self.dhcp_expander.get_expanded()
1774 if expanded:
1775 self.dhcp_expander.set_label( USE_IP_LABEL )
1776 else:
1777 self.dhcp_expander.set_label( USE_DHCP_LABEL )
1779 # Respond to expanding/hiding WPA segment.
1781 #Parameters:
1783 # 'widget' -- gtk.Widget - The widget sending the event.
1785 # 'data' -- tuple - List of arbitrary arguments (not used)
1787 #Returns:
1789 # nothing
1790 def toggle_use_wpa( self, widget, data = None ):
1791 expanded = self.wpa_expander.get_expanded()
1792 if expanded:
1793 self.wpa_expander.set_label( USE_WPA_LABEL )
1794 else:
1795 self.wpa_expander.set_label( NO_WPA_LABEL )
1797 # Return the index where item matches a cell in array.
1799 #Parameters:
1801 # 'item' -- string - Item to find in array
1803 # 'array' -- list - List in which to find match.
1805 #Returns:
1807 # integer - 0 (no match) or higher (index of match)
1808 def get_array_index( self, item, array ):
1809 try:
1810 return array.index( item.strip() )
1811 except:
1812 pass
1813 return 0
1815 # Return the value in array[ index ]
1817 #Parameters:
1819 # 'index' -- integer - The index to look up.
1821 # 'array' -- list - List in which to look up value.
1823 #Returns:
1825 # string -- empty string (no match) or looked up value
1826 def get_array_item( self, index, array ):
1827 try:
1828 return array[ index ]
1829 except:
1830 pass
1831 return ''
1833 # A simple class for putting up a "Please wait" dialog so the user
1834 # doesn't think we've forgotten about them.
1835 class status_window:
1836 # Create a new status_window.
1838 #Parameters:
1840 # 'parent' -- gtk.Object - Usually, the calling window.
1842 #Returns:
1844 # status_window instance
1845 def __init__( self, parent ):
1846 global wifi_radar_icon
1847 self.parent = parent
1848 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
1849 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1850 self.dialog.set_icon( icon )
1851 self.lbl = gtk.Label("Please wait...")
1852 self.bar = gtk.ProgressBar()
1853 self.dialog.vbox.pack_start(self.lbl)
1854 self.dialog.vbox.pack_start(self.bar)
1855 self.dialog.show_all()
1857 # Change the message displayed to the user.
1859 #Parameters:
1861 # 'message' -- string - The message to show to the user.
1863 #Returns:
1865 # nothing
1866 def update_message( self, message ):
1867 self.lbl.set_text(message)
1869 # Update the status_window progress bar.
1871 #Parameters:
1873 # nothing
1875 #Returns:
1877 # True -- always return True
1878 def update_window( self ):
1879 self.bar.pulse()
1880 return True
1882 # Display and operate the status_window.
1884 #Parameters:
1886 # nothing
1888 #Returns:
1890 # nothing
1891 def run( self ):
1892 self.dialog.show_all()
1893 self.timer = gobject.timeout_add(250,self.update_window)
1894 return
1896 # Remove the status_window.
1898 #Parameters:
1900 # nothing
1902 #Returns:
1904 # nothing
1905 def destroy( self ):
1906 gobject.source_remove(self.timer)
1907 self.dialog.destroy()
1908 del self.dialog
1911 # Manage a GTK About Dialog
1912 class about_dialog(gtk.AboutDialog):
1913 # Subclass GTK AboutDialog
1915 #Parameters:
1917 # nothing
1919 #Returns:
1921 # nothing
1922 def __init__( self ):
1923 global wifi_radar_icon
1925 gtk.AboutDialog.__init__(self)
1926 self.set_authors(["Ahmad Baitalmal <ahmad@baitalmal.com>", "Brian Elliott Finley <brian@thefinleys.com>", "Sean Robinson <seankrobinson@gmail.com>", "", "Contributors", "Douglas Breault", "Jon Collette", "David Decotigny", "Simon Gerber", "Joey Hurst", u"Ante Karamati\xc4\x87", "Richard Monk", "Brouard Nicolas", "Kevin Otte", "Nathanael Rebsch", "Patrick Winnertz"])
1927 self.set_comments("WiFi connection manager")
1928 self.set_copyright("Copyright 2004-2009 by various authors and contributors")
1929 self.set_documenters(["Gary Case"])
1930 license = """
1931 This program is free software; you can redistribute it and/or modify
1932 it under the terms of the GNU General Public License as published by
1933 the Free Software Foundation; either version 2 of the License, or
1934 (at your option) any later version.
1936 This program is distributed in the hope that it will be useful,
1937 but WITHOUT ANY WARRANTY; without even the implied warranty of
1938 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1939 GNU General Public License for more details.
1941 You should have received a copy of the GNU General Public License
1942 along with this program; if not, write to the Free Software
1943 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
1944 self.set_license(license)
1945 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1946 self.set_logo(logo)
1947 self.set_name("WiFi Radar")
1948 self.set_version(WIFI_RADAR_VERSION)
1949 self.set_website("http://developer.berlios.de/projects/wifi-radar/")
1953 # Manage the configuration for the application, including reading and writing the config from/to a file.
1954 class ConfigFile(ConfigParser.SafeConfigParser):
1955 # Create a new ConfigFile.
1957 #Parameters:
1959 # 'filename' -- string - The configuration file's name.
1961 # 'defaults' -- dictionary - Default values for the DEFAULT section.
1963 #Returns:
1965 # ConfigFile instance
1966 def __init__( self, filename, defaults ):
1967 self.filename = filename
1968 self.auto_profile_order = []
1969 ConfigParser.SafeConfigParser.__init__(self, defaults)
1971 # Set the contents of a section to values from a dictionary.
1973 #Parameters:
1975 # 'section_name' -- string - Configuration file section.
1977 # 'section_dict' -- dictionary - Values to add to section.
1979 #Returns:
1981 # nothing
1982 def set_section( self, section_name, section_dict ):
1983 try:
1984 self.add_section(section_name)
1985 except ConfigParser.DuplicateSectionError:
1986 pass
1987 for key in section_dict.keys():
1988 if type(section_dict[key]) == BooleanType:
1989 self.set_bool_opt(section_name + "." + key, section_dict[key])
1990 elif type(section_dict[key]) == IntType:
1991 self.set_int_opt(section_name + "." + key, section_dict[key])
1992 elif type(section_dict[key]) == FloatType:
1993 self.set_float_opt(section_name + "." + key, section_dict[key])
1994 else:
1995 self.set_opt(section_name + "." + key, section_dict[key])
1997 # Return the profile recorded in the specified section.
1999 #Parameters:
2001 # 'section_name' -- string - Configuration file section.
2003 #Returns:
2005 # dictionary or None - The specified profile or None if not found
2006 def get_profile( self, section_name ):
2007 if section_name in self.profiles():
2008 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' ]
2009 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
2010 int_types = [ 'signal' ]
2011 profile = {}
2012 for option in bool_types:
2013 profile[option] = self.get_opt_as_bool( section_name + "." + option )
2014 for option in int_types:
2015 profile[option] = self.get_opt_as_int( section_name + "." + option )
2016 for option in str_types:
2017 profile[option] = self.get_opt( section_name + "." + option )
2018 return profile
2019 return None
2021 # Get a config option and handle exceptions.
2023 #Parameters:
2025 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2026 # period and the option key. (E.g. "DEFAULT.interface")
2028 #Returns:
2030 # string or None - option value as string or None on failure
2031 def get_opt( self, option_path ):
2032 #print "ConfigFile.get_opt: ", option_path
2033 (section, option) = option_path.split('.')
2034 try:
2035 return self.get(section, option)
2036 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2037 return None
2039 # Get a config option and return as a boolean type.
2041 #Parameters:
2043 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2044 # period and the option key. (E.g. "DEFAULT.interface")
2046 #Returns:
2048 # boolean - option value as boolean
2049 def get_opt_as_bool( self, option_path ):
2050 option = self.get_opt(option_path)
2051 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2052 return option
2053 if option == 'True':
2054 return True
2055 if option == 'False':
2056 return False
2057 raise ValueError, 'boolean option was not True or False'
2059 # Get a config option and return as an integer type.
2061 #Parameters:
2063 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2064 # period and the option key. (E.g. "DEFAULT.interface")
2066 #Returns:
2068 # integer- option value as integer
2069 def get_opt_as_int( self, option_path ):
2070 return int(float(self.get_opt(option_path)))
2072 # Convert boolean type to string and set config option.
2074 #Parameters:
2076 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2077 # period and the option key. (E.g. "DEFAULT.interface")
2079 # 'value' -- boolean - Value to set.
2081 #Returns:
2083 # nothing
2084 def set_bool_opt( self, option_path, value ):
2085 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2086 value == 'True'
2087 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2088 value == 'False'
2089 else:
2090 raise ValueError, 'cannot convert value to string'
2091 self.set_opt(option_path, repr(value))
2093 # Convert integer type to string and set config option.
2095 #Parameters:
2097 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2098 # period and the option key. (E.g. "DEFAULT.interface")
2100 # 'value' -- integer - Value to set.
2102 #Returns:
2104 # nothing
2105 def set_int_opt( self, option_path, value ):
2106 if not isinstance(value, IntType):
2107 raise ValueError, 'value is not an integer'
2108 self.set_opt(option_path, repr(value))
2110 # Convert float type to string and set config option.
2112 #Parameters:
2114 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2115 # period and the option key. (E.g. "DEFAULT.interface")
2117 # 'value' -- float - Value to set.
2119 #Returns:
2121 # nothing
2122 def set_float_opt( self, option_path, value ):
2123 if not isinstance(value, FloatType):
2124 raise ValueError, 'value is not a float'
2125 self.set_opt(option_path, repr(int(value)))
2127 # Set a config option while handling exceptions.
2129 #Parameters:
2131 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2132 # period and the option key. (E.g. "DEFAULT.interface")
2134 # 'value' -- string - Value to set.
2136 #Returns:
2138 # nothing
2139 def set_opt( self, option_path, value ):
2140 (section, option) = option_path.split('.')
2141 try:
2142 self.set(section, option, value)
2143 except ConfigParser.NoSectionError:
2144 self.add_section(section)
2145 self.set_opt(option_path, value)
2147 # Return a list of the section names which denote AP profiles.
2149 #Parameters:
2151 # nothing
2153 #Returns:
2155 # list - profile names
2156 def profiles( self ):
2157 profile_list = []
2158 for section in self.sections():
2159 if ':' in section:
2160 profile_list.append(section)
2161 return profile_list
2163 # Read configuration file from disk into instance variables.
2165 #Parameters:
2167 # nothing
2169 #Returns:
2171 # nothing
2172 def read( self ):
2173 fp = open( self.filename, "r" )
2174 self.readfp(fp)
2175 # convert the auto_profile_order to a list for ordering
2176 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2177 for ap in self.profiles():
2178 self.set_bool_opt( ap + '.known', True)
2179 if ap in self.auto_profile_order: continue
2180 self.auto_profile_order.append( ap )
2182 # Write configuration file to disk from instance variables. Copied from
2183 # ConfigParser and modified to write options in alphabetical order.
2185 #Parameters:
2187 # nothing
2189 #Returns:
2191 # nothing
2192 def write( self ):
2193 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2194 fp = open( self.filename, "w" )
2195 # write DEFAULT section first
2196 if self._defaults:
2197 fp.write("[DEFAULT]\n")
2198 for key in sorted(self._defaults.keys()):
2199 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2200 fp.write("\n")
2201 # write non-profile sections first
2202 for section in self._sections:
2203 if section not in self.profiles():
2204 fp.write("[%s]\n" % section)
2205 for key in sorted(self._sections[section].keys()):
2206 if key != "__name__":
2207 fp.write("%s = %s\n" %
2208 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2209 fp.write("\n")
2210 # write profile sections
2211 for section in self._sections:
2212 if section in self.profiles():
2213 fp.write("[%s]\n" % section)
2214 for key in sorted(self._sections[section].keys()):
2215 if key != "__name__":
2216 fp.write("%s = %s\n" %
2217 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2218 fp.write("\n")
2222 # Load our conf file and known profiles
2223 # Defaults, these may get overridden by values found in the conf file.
2224 config_defaults = { # The network interface you use.
2225 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2226 'interface': "auto_detect",
2227 # How long should the scan for access points last?
2228 'scan_timeout': '5',
2229 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2230 # Set the speak_up option to false if you do not have or want this.
2231 'speak_command': '/usr/bin/say',
2232 # Should I speak up when connecting to a network? (If you have a speech command)
2233 'speak_up': 'False',
2234 # You may set this to true for cards that require a "commit" command with iwconfig
2235 'commit_required': 'False',
2236 # You may set this to true for cards that require the interface to be brought up first
2237 'ifup_required': 'False',
2238 # Set the location of several important programs
2239 'iwlist_command': '/sbin/iwlist',
2240 'iwconfig_command': '/sbin/iwconfig',
2241 'ifconfig_command': '/sbin/ifconfig',
2242 'route_command': '/sbin/route',
2243 'auto_profile_order': '[]',
2244 'version': WIFI_RADAR_VERSION }
2246 config_dhcp = { # DHCP client
2247 'command': 'dhcpcd',
2248 # How long to wait for an IP addr from DHCP server
2249 'timeout': '30',
2250 # Arguments to use with DHCP client on connect
2251 'args': '-D -o -i dhcp_client -t %(timeout)s',
2252 # Argument to use with DHCP client on disconnect
2253 'kill_args': '-k',
2254 # The file where DHCP client PID is written
2255 'pidfile': '/etc/dhcpcd-%(interface)s.pid' }
2257 config_wpa = { # WPA Supplicant
2258 'command': '/usr/sbin/wpa_supplicant',
2259 # Arguments to use with WPA Supplicant on connect
2260 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2261 # Arguments to use with WPA Supplicant on disconnect
2262 'kill_command': '',
2263 # Where the WPA Supplicant config file can be found
2264 'configuration': '/etc/wpa_supplicant.conf',
2265 # Driver to use with WPA Supplicant
2266 'driver': 'wext',
2267 # The file where WPA Supplicant PID is written
2268 'pidfile': '/var/run/wpa_supplicant.pid' }
2270 # initialize config, with defaults
2271 confFile = ConfigFile(CONF_FILE, config_defaults)
2272 confFile.set_section("DHCP", config_dhcp)
2273 confFile.set_section("WPA", config_wpa)
2275 if not os.path.isfile( CONF_FILE ):
2276 confFile.set_bool_opt('DEFAULT.new_file', True)
2277 else:
2278 if not os.access(CONF_FILE, os.R_OK):
2279 print "Can't open " + CONF_FILE + "."
2280 print "Are you root?"
2281 sys.exit()
2282 confFile.read()
2285 ####################################################################################################
2286 # Embedded Images
2287 wifi_radar_icon = [ ""
2288 "GdkP"
2289 "\0\0\22""7"
2290 "\2\1\0\2"
2291 "\0\0\1\214"
2292 "\0\0\0c"
2293 "\0\0\0O"
2294 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2295 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2296 "\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"
2297 "\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"
2298 "\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"
2299 "\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"
2300 "\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"
2301 "\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"
2302 "\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"
2303 "\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"
2304 "\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"
2305 "\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"
2306 "\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"
2307 "\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"
2308 "\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"
2309 "\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"
2310 "\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"
2311 "\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"
2312 "\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"
2313 "\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"
2314 "\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"
2315 "\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"
2316 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2317 "\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"
2318 "\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"
2319 "\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"
2320 "\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"
2321 "\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"
2322 "\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"
2323 "\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"
2324 "\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"
2325 "\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"
2326 "\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"
2327 "\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"
2328 "\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"
2329 "\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"
2330 "\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"
2331 "\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"
2332 "\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"
2333 "\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"
2334 "\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"
2335 "\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"
2336 "\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"
2337 "\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"
2338 "\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"
2339 "\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"
2340 "\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"
2341 "\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"
2342 "\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"
2343 "\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"
2344 "\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"
2345 "\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"
2346 "\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"
2347 "\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"
2348 "\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"
2349 "\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"
2350 "\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"
2351 "\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"
2352 "\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"
2353 "\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"
2354 "\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"
2355 "\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"
2356 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2357 "\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"
2358 "\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"
2359 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2360 "\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"
2361 "\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"
2362 "\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"
2363 "\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"
2364 "\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"
2365 "\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"
2366 "\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"
2367 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2368 "\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"
2369 "\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"
2370 "\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"
2371 "\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"
2372 "\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"
2373 "\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"
2374 "|\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"
2375 "\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"
2376 "\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"
2377 "\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"
2378 "\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"
2379 "\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"
2380 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2381 "\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"
2382 "\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"
2383 "\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"
2384 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2385 "\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"
2386 "\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"
2387 "\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"
2388 "\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"
2389 "\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"
2390 "\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"
2391 "\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"
2392 "\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"
2393 "\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"
2394 "\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"
2395 "\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"
2396 "\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"
2397 "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"
2398 "\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"
2399 "\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"
2400 "\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"
2401 "\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"
2402 "\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"
2403 "\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"
2404 "\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"
2405 "\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"
2406 "\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"
2407 "\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"
2408 "\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"
2409 "\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"
2410 "\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"
2411 "\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"
2412 "\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|"
2413 "\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"
2414 "\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"
2415 "\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"
2416 "\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"
2417 "\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"
2418 "\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"
2419 "\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"
2420 "\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"
2421 "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"
2422 "\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"
2423 "\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"
2424 "\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"
2425 "\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"
2426 "\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"
2427 "\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"
2428 "\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"
2429 "\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"
2430 "\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"
2431 "\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"
2432 "\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"
2433 "\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"
2434 "\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"
2435 "\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"
2436 "\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"
2437 "\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"
2438 "\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"
2439 "\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"
2440 "\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"
2441 "\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"
2442 "\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"
2443 "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"
2444 "\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"
2445 "\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"
2446 "\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"
2447 "\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"
2448 "\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"
2449 "\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"
2450 "\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"
2451 "\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"
2452 "\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"
2453 "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"
2454 "\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"
2455 "\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"
2456 "\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"
2457 "\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"
2458 "\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"
2459 "\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"
2460 "\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"
2461 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2462 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2463 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2464 "\0"]
2466 known_profile_icon = [ ""
2467 "GdkP"
2468 "\0\0\5""0"
2469 "\2\1\0\2"
2470 "\0\0\0P"
2471 "\0\0\0\24"
2472 "\0\0\0\24"
2473 "\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"
2474 "\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"
2475 "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"
2476 "\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"
2477 "\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"
2478 "\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"
2479 "\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"
2480 "\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"
2481 "\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"
2482 "\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"
2483 "\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"
2484 "\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"
2485 "\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"
2486 "\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"
2487 "\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"
2488 "\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"
2489 "\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"
2490 "\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"
2491 "\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"
2492 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2493 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2494 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2495 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2496 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2497 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2498 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2499 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2500 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2501 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2502 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2503 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2504 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2505 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2506 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2507 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2508 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2509 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2510 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2511 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2512 "\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"
2513 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2514 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2515 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2516 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2517 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2518 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2519 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2520 "\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"
2521 "\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"
2522 "\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"
2523 "\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"
2524 "\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"]
2526 unknown_profile_icon = [ ""
2527 "GdkP"
2528 "\0\0\5\22"
2529 "\2\1\0\2"
2530 "\0\0\0P"
2531 "\0\0\0\24"
2532 "\0\0\0\24"
2533 "\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"
2534 "\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"
2535 "\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"
2536 "\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"
2537 "(\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"
2538 "\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"
2539 "#\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"
2540 "\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"
2541 "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"
2542 "\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"
2543 "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"
2544 "\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"
2545 "\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"
2546 "\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"
2547 "\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"
2548 "\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"
2549 "\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"
2550 "\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"
2551 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2552 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2553 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2554 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2555 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2556 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2557 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2558 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2559 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2560 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2561 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2562 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2563 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2564 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2565 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2566 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2567 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2568 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2569 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2570 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2571 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2572 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2573 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2574 "\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"
2575 "\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"
2576 "\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"
2577 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2579 signal_xpm_barely = [
2580 "20 20 10 1",
2581 " c None",
2582 ". c #C6C6C6",
2583 "+ c #CCCCCC",
2584 "@ c #DBDBDB",
2585 "# c #D3D3D3",
2586 "$ c #A9B099",
2587 "% c #95A173",
2588 "& c #6B8428",
2589 "* c #B4B7AC",
2590 "= c #80924D",
2591 " .+++.",
2592 " +@@@+",
2593 " +@@@+",
2594 " +@@@+",
2595 " +@@@+",
2596 " .++++#@@@+",
2597 " +@@@@@@@@+",
2598 " +@@@@@@@@+",
2599 " +@@@@@@@@+",
2600 " +@@@@@@@@+",
2601 " $%%%%#@@@@@@@@+",
2602 " %&&&&@@@@@@@@@+",
2603 " %&&&&@@@@@@@@@+",
2604 " %&&&&@@@@@@@@@+",
2605 " %&&&&@@@@@@@@@+",
2606 "*%%%%=&&&&@@@@@@@@@+",
2607 "%&&&&&&&&&@@@@@@@@@+",
2608 "%&&&&&&&&&@@@@@@@@@+",
2609 "%&&&&&&&&&@@@@@@@@@+",
2610 "*%%%%%%%%%+++++++++."
2614 signal_xpm_best = [
2615 "20 20 6 1",
2616 " c None",
2617 ". c #9DAABF",
2618 "+ c #7B96BF",
2619 "@ c #386EBF",
2620 "# c #5982BF",
2621 "$ c #AEB4BF",
2622 " .+++.",
2623 " +@@@+",
2624 " +@@@+",
2625 " +@@@+",
2626 " +@@@+",
2627 " .++++#@@@+",
2628 " +@@@@@@@@+",
2629 " +@@@@@@@@+",
2630 " +@@@@@@@@+",
2631 " +@@@@@@@@+",
2632 " .++++#@@@@@@@@+",
2633 " +@@@@@@@@@@@@@+",
2634 " +@@@@@@@@@@@@@+",
2635 " +@@@@@@@@@@@@@+",
2636 " +@@@@@@@@@@@@@+",
2637 "$++++#@@@@@@@@@@@@@+",
2638 "+@@@@@@@@@@@@@@@@@@+",
2639 "+@@@@@@@@@@@@@@@@@@+",
2640 "+@@@@@@@@@@@@@@@@@@+",
2641 "$++++++++++++++++++."
2644 signal_xpm_none = [
2645 "20 20 6 1",
2646 " c None",
2647 ". c #C6C6C6",
2648 "+ c #CCCCCC",
2649 "@ c #DBDBDB",
2650 "# c #D3D3D3",
2651 "$ c #C2C2C2",
2652 " .+++.",
2653 " +@@@+",
2654 " +@@@+",
2655 " +@@@+",
2656 " +@@@+",
2657 " .++++#@@@+",
2658 " +@@@@@@@@+",
2659 " +@@@@@@@@+",
2660 " +@@@@@@@@+",
2661 " +@@@@@@@@+",
2662 " .++++#@@@@@@@@+",
2663 " +@@@@@@@@@@@@@+",
2664 " +@@@@@@@@@@@@@+",
2665 " +@@@@@@@@@@@@@+",
2666 " +@@@@@@@@@@@@@+",
2667 "$++++#@@@@@@@@@@@@@+",
2668 "+@@@@@@@@@@@@@@@@@@+",
2669 "+@@@@@@@@@@@@@@@@@@+",
2670 "+@@@@@@@@@@@@@@@@@@+",
2671 "$++++++++++++++++++."
2674 signal_xpm_ok = [
2675 "20 20 10 1",
2676 " c None",
2677 ". c #C6C6C6",
2678 "+ c #CCCCCC",
2679 "@ c #DBDBDB",
2680 "# c #A1A5B2",
2681 "$ c #848DA5",
2682 "% c #D3D3D3",
2683 "& c #4A5B8C",
2684 "* c #677498",
2685 "= c #B0B2B8",
2686 " .+++.",
2687 " +@@@+",
2688 " +@@@+",
2689 " +@@@+",
2690 " +@@@+",
2691 " #$$$$%@@@+",
2692 " $&&&&@@@@+",
2693 " $&&&&@@@@+",
2694 " $&&&&@@@@+",
2695 " $&&&&@@@@+",
2696 " #$$$$*&&&&@@@@+",
2697 " $&&&&&&&&&@@@@+",
2698 " $&&&&&&&&&@@@@+",
2699 " $&&&&&&&&&@@@@+",
2700 " $&&&&&&&&&@@@@+",
2701 "=$$$$*&&&&&&&&&@@@@+",
2702 "$&&&&&&&&&&&&&&@@@@+",
2703 "$&&&&&&&&&&&&&&@@@@+",
2704 "$&&&&&&&&&&&&&&@@@@+",
2705 "=$$$$$$$$$$$$$$++++."
2709 signal_xpm_low = [
2710 "20 20 8 1",
2711 " c None",
2712 ". c #C6C6C6",
2713 "+ c #CCCCCC",
2714 "@ c #DBDBDB",
2715 "# c #D3D3D3",
2716 "$ c #BFB0B5",
2717 "% c #C18799",
2718 "& c #C54F74",
2719 " .+++.",
2720 " +@@@+",
2721 " +@@@+",
2722 " +@@@+",
2723 " +@@@+",
2724 " .++++#@@@+",
2725 " +@@@@@@@@+",
2726 " +@@@@@@@@+",
2727 " +@@@@@@@@+",
2728 " +@@@@@@@@+",
2729 " .++++#@@@@@@@@+",
2730 " +@@@@@@@@@@@@@+",
2731 " +@@@@@@@@@@@@@+",
2732 " +@@@@@@@@@@@@@+",
2733 " +@@@@@@@@@@@@@+",
2734 "$%%%%#@@@@@@@@@@@@@+",
2735 "%&&&&@@@@@@@@@@@@@@+",
2736 "%&&&&@@@@@@@@@@@@@@+",
2737 "%&&&&@@@@@@@@@@@@@@+",
2738 "$%%%%++++++++++++++."
2742 ####################################################################################################
2743 # Make so we can be imported
2744 if __name__ == "__main__":
2745 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
2746 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
2747 else:
2748 import gtk, gobject
2749 gtk.gdk.threads_init()
2750 apQueue = Queue.Queue(100)
2751 commQueue = Queue.Queue(2)
2752 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue ) ).start()
2753 main_radar_window = radar_window(confFile, apQueue, commQueue)
2754 gobject.timeout_add( 500, main_radar_window.update_plist_items )
2755 main_radar_window.main()