Add security mode to associate attempt
[wifi-radar.git] / wifi-radar
blob1a4a1501ad579a1ce947682df5b29d7513a0e52b
1 #!/usr/bin/python
3 # A wireless profile manager for Linux
5 # Originally created for x1000 Linux:
6 # http://x1000.bitbuilder.com
8 # Created by:
9 # Ahmad Baitalmal <ahmad@baitalmal.com>
11 # Maintained 2006-2009 by:
12 # Brian Elliott Finley <brian@thefinleys.com>
14 # Maintained by:
15 # Sean Robinson <seankrobinson@gmail.com>
17 # License:
18 # GPL
20 # http://wifi-radar.berlios.de
22 # See CREDITS file for more contributors.
23 # See ChangeLog file for, well, changes.
25 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
26 # turn on console debugging.
28 import ConfigParser
29 import errno
30 import gtk
31 import logging
32 import logging.handlers
33 import os
34 import Queue
35 import re
36 import string
37 import sys
38 import threading
39 from signal import SIGTERM
40 from subprocess import call, Popen, PIPE
41 from time import sleep
42 from types import *
44 WIFI_RADAR_VERSION = "0.0.0"
47 # Where the conf file should live could be different for your distro. Please change
48 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
50 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
52 os.environ['LC_MESSAGES'] = 'C'
55 ####################################################################################################
56 ####################################################################################################
58 # Sets the interface to the specified network device
60 #Parameters:
62 # 'device' -- string - The network device to use
64 #Returns:
66 # nothing
67 def set_network_device( device ):
68 #print "set_network_device: ", device
69 if device != "auto_detect":
70 confFile.set_opt('DEFAULT.interface', device)
71 else: # auto detect network device
72 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
73 # If no devices are found, default to eth1.
74 # call iwconfig command and read output
75 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
76 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
77 if len(wireless_devices) > 0:
78 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
79 #else:
80 #print "No wifi-device found. Exiting."
81 #sys.exit()
83 # Return a blank profile
85 #Parameters:
87 # none
89 #Returns:
91 # dictionary -- An AP profile with defaults set.
92 def get_new_profile():
93 return { 'known': False,
94 'available': False,
95 'encrypted': False,
96 'essid': '',
97 'bssid': '',
98 'roaming': False,
99 'protocol': 'g',
100 'signal': 0,
101 'channel': 'auto',
102 'con_prescript': '',
103 'con_postscript': '',
104 'dis_prescript': '',
105 'dis_postscript': '',
106 'key': '',
107 'mode': 'auto',
108 'security': '',
109 'use_wpa': False,
110 'wpa_driver': '',
111 'use_dhcp': True,
112 'ip': '',
113 'netmask': '',
114 'gateway': '',
115 'domain': '',
116 'dns1': '',
117 'dns2': ''
120 # Combine essid and bssid to make a config file section name
122 #Parameters:
124 # 'essid' -- string - AP ESSID
126 # 'bssid' -- string - AP BSSID
128 #Returns:
130 # string -- the bssid concatenated to a colon, concatenated to the essid
131 def make_section_name( essid, bssid ):
132 return essid + ':' + bssid
134 # Split a config file section name into an essid and a bssid
136 #Parameters:
138 # 'section' -- string - Config file section name
140 #Returns:
142 # list -- the essid and bssid
143 def split_section_name( section ):
144 parts = re.split(':', section)
145 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
147 # Run commands through the shell
149 #Parameters:
151 # 'command' -- tuple - The command and arguments to run.
153 # 'environment' -- dictionary - Environment variables (as keys) and their values.
155 #Returns:
157 # boolean -- True on success, otherwise, False
158 def shellcmd( command, environment = None ):
159 try:
160 env_tmp = os.environ
161 env_tmp.update(environment)
162 command = ' '.join(command)
163 return_code = call(command, shell=True, env=env_tmp)
164 if return_code >= 0:
165 return True
166 else:
167 print >>sys.stderr, "Child was terminated by signal", -return_code
168 except OSError, exception:
169 print >>sys.stderr, "Execution failed:", exception
170 return False
172 # Speak feedback message to user
174 #Parameters:
176 # 'words' -- string - Message to speak to user
178 #Returns:
180 # nothing
181 def say( words ):
182 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
183 words = words.replace( "\"", "\\\"" )
184 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
186 # Scan for a limited time and return AP names and bssid found.
187 # Access points we find will be put on the outgoing Queue, apQueue.
189 #Parameters:
191 # 'confFile' -- ConfigFile - Config file object
193 # 'apQueue' -- Queue - Queue on which to put AP profiles
195 # 'commandQueue' -- Queue - Queue from which to read commands
197 # 'logger' -- Logger - Python's logging facility
199 #Returns:
201 # nothing
202 def scanning_thread(confFile, apQueue, commandQueue, logger, exit_event):
203 logger.info("Begin thread.")
204 # Setup our essid pattern matcher
205 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
206 bssid_pattern = re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
207 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S )
208 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
209 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
210 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
211 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
213 access_points = {}
214 command = "scan"
215 while True:
216 try:
217 command = commandQueue.get_nowait()
218 logger.info("received command: %s" % (command, ))
219 command_read = True
220 except Queue.Empty:
221 command_read = False
222 if command == "scan":
223 logger.debug("Beginning scan pass")
224 # Some cards need to have the interface up to scan
225 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
226 # call ifconfig command and wait for return
227 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
228 # update the signal strengths
229 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
230 # zero out the signal levels for all access points
231 for bssid in access_points:
232 access_points[bssid]['signal'] = 0
233 # split the scan data based on the address line
234 hits = scandata.split(' - ')
235 for hit in hits:
236 # set the defaults for profile template
237 profile = get_new_profile()
238 m = essid_pattern.search( hit )
239 if m:
240 # we found an essid
241 profile['essid'] = m.groups()[1]
242 m = bssid_pattern.search( hit ) # get BSSID from scan
243 if m: profile['bssid'] = m.groups()[1]
244 m = protocol_pattern.search( hit ) # get protocol from scan
245 if m: profile['protocol'] = m.groups()[1]
246 m = mode_pattern.search( hit ) # get mode from scan
247 if m: profile['mode'] = m.groups()[1]
248 m = channel_pattern.search( hit ) # get channel from scan
249 if m: profile['channel'] = m.groups()[1]
250 m = enckey_pattern.search( hit ) # get encryption key from scan
251 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
252 m = signal_pattern.search( hit ) # get signal strength from scan
253 if m: profile['signal'] = m.groups()[1]
254 access_points[ profile['bssid'] ] = profile
255 for bssid in access_points:
256 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
257 # Put all, now or previously, sensed access_points into apQueue
258 try:
259 logger.debug("Scanned profile: %s" % (access_points[ bssid ], ))
260 apQueue.put_nowait( access_points[bssid] )
261 except Queue.Full:
262 pass
263 if command_read:
264 commandQueue.task_done()
265 if exit_event.isSet():
266 logger.info("Exiting.")
267 return
268 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
269 sleep( 3 )
270 else:
271 sleep( 1 )
274 # Manage a connection; including reporting connection state,
275 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
276 class ConnectionManager():
277 # Create a new connection manager which can read a config file and send to scanning thread
278 # command Queue. A new manager checks for a pre-existing connection and takes
279 # its AP profile from the ESSID and BSSID to which it is currently attached.
281 #Parameters:
283 # 'confFile' -- ConfigFile - Config file object
285 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
287 # 'logger' -- Logger - Python's logging facility
289 #Returns:
291 # ConnectionManager instance
292 def __init__( self, confFile, commandQueue, logger ):
293 self.confFile = confFile
294 self.commQueue = commandQueue
295 self.logger = logger
296 # is connection running?
297 self.state = False
298 if self.get_current_ip():
299 self.state = True
300 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
302 # Change the interface state: up or down.
304 #Parameters:
306 # 'state' -- string - The state to which to change the interface.
308 #Returns:
310 # nothing
311 def if_change( self, state ):
312 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
313 self.logger.info("changing interface state to %s" % (state, ))
314 # call ifconfig command and wait for return
315 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
317 # Connect to the specified AP.
319 #Parameters:
321 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
323 # 'status' -- status implementer - Object which implements status interface.
325 #Returns:
327 # nothing
328 def connect_to_network( self, profile, status ):
329 self.profile = profile
330 if self.profile['bssid'] == '':
331 raise TypeError("Empty AP address")
332 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
333 say( msg )
334 self.logger.info(msg)
335 # ready to dance
336 # Let's run the connection prescript
337 if self.profile['con_prescript'].strip() != '':
338 # got something to execute
339 # run connection prescript through shell and wait for return
340 self.logger.info("executing connection prescript: %s" % (self.profile['con_prescript'], ))
341 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
342 status.show()
343 # Some cards need to have the interface up
344 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
345 self.if_change('up')
346 # Start building iwconfig command line, command
347 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
348 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
349 # Setting essid
350 iwconfig_command.append( 'essid' )
351 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
352 # Setting nick
353 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
354 # Setting key
355 iwconfig_command.append( 'key' )
356 if self.profile['key'] == '':
357 iwconfig_command.append( 'off' )
358 else:
359 iwconfig_command.append( "'" + self.profile['key'] + "'" )
360 iwconfig_command.append(self.profile['security'])
361 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
362 # Setting mode
363 if self.profile['mode'].lower() == 'master':
364 self.profile['mode'] = 'Managed'
365 iwconfig_command.append( 'mode' )
366 iwconfig_command.append( self.profile['mode'] )
367 # Setting channel
368 if self.profile['channel'] != '':
369 iwconfig_command.append( 'channel' )
370 iwconfig_command.append( self.profile['channel'] )
371 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
372 iwconfig_command.append( 'ap' )
373 iwconfig_command.append( self.profile['bssid'] )
374 # Some cards require a commit
375 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
376 iwconfig_command.append( 'commit' )
377 self.logger.info("iwconfig_command: %s" % (iwconfig_command, ))
378 # call iwconfig command and wait for return
379 if not shellcmd(iwconfig_command): return
380 # Now normal network stuff
381 # Kill off any existing DHCP clients running
382 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
383 self.logger.info("Killing existing DHCP...")
384 try:
385 if self.confFile.get_opt('DHCP.kill_args') != '':
386 # call DHCP client kill command and wait for return
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 self.logger.info("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 self.logger.info("WPA args: %s" % (self.confFile.get_opt('WPA.args'), ))
414 status.update_message("WPA supplicant starting")
415 if sys.modules.has_key("gtk"):
416 while gtk.events_pending():
417 gtk.main_iteration(False)
418 # call WPA supplicant command and do not wait for return
419 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
420 if self.profile['use_dhcp'] :
421 self.logger.debug("Disable iwlist while dhcp in progress...")
422 try:
423 self.commQueue.put("pause")
424 except Queue.Full:
425 pass
426 status.update_message("Acquiring IP Address (DHCP)")
427 if sys.modules.has_key("gtk"):
428 while gtk.events_pending():
429 gtk.main_iteration(False)
430 # call DHCP client command and do not wait for return
431 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
432 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
433 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
434 self.logger.info("dhcp_command: %s" % (dhcp_command, ))
435 dhcp_proc = Popen(dhcp_command, stdout=None)
436 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
437 tick = 0.25
438 waiting = dhcp_proc.poll()
439 while waiting == None:
440 waiting = dhcp_proc.poll()
441 if timer < 0:
442 os.kill(dhcp_proc.pid, SIGTERM)
443 break
444 if sys.modules.has_key("gtk"):
445 while gtk.events_pending():
446 gtk.main_iteration(False)
447 timer -= tick
448 sleep(tick)
449 # Re-enable iwlist
450 try:
451 self.commQueue.put("scan")
452 except Queue.Full:
453 pass
454 if not self.get_current_ip():
455 status.update_message("Could not get IP address!")
456 if sys.modules.has_key("gtk"):
457 while gtk.events_pending():
458 gtk.main_iteration(False)
459 sleep(1)
460 if self.state:
461 self.disconnect_interface()
462 status.hide()
463 return
464 else:
465 status.update_message("Got IP address. Done.")
466 self.state = True
467 if sys.modules.has_key("gtk"):
468 while gtk.events_pending():
469 gtk.main_iteration(False)
470 sleep(2)
471 else:
472 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'] )
473 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
474 resolv_contents = ''
475 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
476 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
477 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
478 if ( resolv_contents != '' ):
479 resolv_file=open('/etc/resolv.conf', 'w')
480 resolv_file.write(s)
481 resolv_file.close
482 if not shellcmd([ifconfig_command]): return
483 if not shellcmd([route_command]): return
484 self.state = True
485 # Let's run the connection postscript
486 con_postscript = self.profile['con_postscript']
487 if self.profile['con_postscript'].strip() != '':
488 self.logger.info("executing connection postscript: %s" % (self.profile['con_postscript'], ))
489 shellcmd([self.profile['con_postscript']],
490 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
491 "WIFIRADAR_ESSID": self.get_current_essid() or '',
492 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
493 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
496 status.hide()
498 # Disconnect from the AP with which a connection has been established/attempted.
500 #Parameters:
502 # nothing
504 #Returns:
506 # nothing
507 def disconnect_interface( self ):
508 msg = "Disconnecting"
509 say( msg )
510 self.logger.info(msg)
511 # Pause scanning while manipulating card
512 try:
513 self.commQueue.put("pause")
514 except Queue.Full:
515 pass
516 if self.state:
517 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
518 # Let's run the disconnection prescript
519 if self.profile['dis_prescript'].strip() != '':
520 self.logger.info("executing disconnection prescript: %s" % (self.profile['dis_prescript'], ))
521 shellcmd([self.profile['dis_prescript']],
522 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
523 "WIFIRADAR_ESSID": self.get_current_essid() or '',
524 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
525 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
528 self.logger.info("Kill off any existing DHCP clients running...")
529 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
530 self.logger.info("Killing existing DHCP...")
531 try:
532 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
533 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
534 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
535 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
536 self.logger.info("DHCP command: %s" % (dhcp_command, ))
537 # call DHCP client command and wait for return
538 if not shellcmd(dhcp_command): return
539 else:
540 self.logger.info("Killing DHCP manually...")
541 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
542 except OSError:
543 print "failed to kill DHCP client"
544 self.logger.info("Kill off any existing WPA supplicants running...")
545 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
546 self.logger.info("Killing existing WPA supplicant...")
547 try:
548 if not self.confFile.get_opt('WPA.kill_command') != '':
549 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
550 if not shellcmd(wpa_command): return
551 else:
552 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
553 except OSError:
554 print "failed to kill WPA supplicant"
555 self.logger.info("Let's clear out the wireless stuff")
556 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
557 self.logger.info("Now take the interface down")
558 self.logger.info("Since it may be brought back up by the next scan, lets unset its IP")
559 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
560 # taking down the interface too quickly can crash my system, so pause a moment
561 sleep(1)
562 self.if_change('down')
563 # Let's run the disconnection postscript
564 if self.profile['dis_postscript'].strip() != '':
565 self.logger.info("executing disconnection postscript: %s" % (self.profile['dis_postscript'], ))
566 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
567 self.state = False
568 self.logger.info("Disconnect complete.")
569 # Begin scanning again
570 try:
571 self.commQueue.put("scan")
572 except Queue.Full:
573 pass
575 # Returns the current IP, if any, by calling ifconfig.
577 #Parameters:
579 # nothing
581 #Returns:
583 # string or None -- the IP address or None (if no there is no current connection)
584 def get_current_ip( self ):
585 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
586 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
587 # Be careful to the language (inet adr: in French for example)
589 # Hi Brian
591 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
592 # There the string in ifconfig is inet Adresse for the IP which isn't
593 # found by the current get_current_ip function in wifi-radar. I changed
594 # the according line (#289; gentoo, v1.9.6-r1) to
595 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
596 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
598 # I'd be happy if you could incorporate this small change because as now
599 # I've got to change the file every time it is updated.
601 # Best wishes
603 # Simon
604 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
605 line = ifconfig_info.read()
606 if ip_re.search( line ):
607 return ip_re.search( line ).group(1)
608 return None
610 # Returns the current ESSID, if any, by calling iwconfig.
612 #Parameters:
614 # nothing
616 #Returns:
618 # string or None -- the ESSID or None (if no there is no current association)
619 def get_current_essid( self ):
620 """Returns the current ESSID if any by calling iwconfig"""
621 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
622 # Be careful to the language (inet adr: in French for example)
623 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
624 line = iwconfig_info.read()
625 if essid_re.search( line ):
626 return essid_re.search( line ).group(2)
627 return None
629 # Returns the current BSSID, if any, by calling iwconfig.
631 #Parameters:
633 # nothing
635 #Returns:
637 # string or None -- the BSSID or None (if no there is no current association)
638 def get_current_bssid( self ):
639 """Returns the current BSSID if any by calling iwconfig"""
640 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
641 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
642 line = iwconfig_info.read()
643 if bssid_re.search( line ):
644 return bssid_re.search( line ).group(2)
645 return None
649 # The main user interface window for WiFi Radar. This class also is the control
650 # center for most of the rest of the operations.
651 class radar_window:
652 # Create a new radar_window.
654 #Parameters:
656 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
658 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
660 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
662 # 'logger' -- Logger - Python's logging facility
664 #Returns:
666 # radar_window instance
667 def __init__(self, confFile, apQueue, commQueue, logger, exit_event):
668 global signal_xpm_none
669 global signal_xpm_low
670 global signal_xpm_barely
671 global signal_xpm_ok
672 global signal_xpm_best
673 global known_profile_icon
674 global unknown_profile_icon
675 global wifi_radar_icon
677 self.confFile = confFile
678 self.apQueue = apQueue
679 self.commandQueue = commQueue
680 self.logger = logger
681 self.access_points = {}
682 self.exit_event = exit_event
683 self.connection = None
685 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
686 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
687 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
688 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
689 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
690 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
691 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
692 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
693 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
694 self.window.set_icon( icon )
695 self.window.set_border_width( 10 )
696 self.window.set_size_request( 550, 300 )
697 self.window.set_title( "WiFi Radar" )
698 self.window.connect( 'delete_event', self.delete_event )
699 self.window.connect( 'destroy', self.destroy )
700 # let's create all our widgets
701 self.current_network = gtk.Label()
702 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
703 self.current_network.show()
704 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
705 self.close_button.show()
706 self.close_button.connect( 'clicked', self.delete_event, None )
707 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
708 self.about_button.show()
709 self.about_button.connect( 'clicked', self.show_about_info, None )
710 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
711 self.preferences_button.show()
712 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
713 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
714 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
715 self.plist = gtk.TreeView( self.pstore )
716 # The icons column, known and encryption
717 self.pix_cell = gtk.CellRendererPixbuf()
718 self.wep_cell = gtk.CellRendererPixbuf()
719 self.icons_cell = gtk.CellRendererText()
720 self.icons_col = gtk.TreeViewColumn()
721 self.icons_col.pack_start( self.pix_cell, False )
722 self.icons_col.pack_start( self.wep_cell, False )
723 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
724 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
725 self.plist.append_column( self.icons_col )
726 # The AP column
727 self.ap_cell = gtk.CellRendererText()
728 self.ap_col = gtk.TreeViewColumn( "Access Point" )
729 self.ap_col.pack_start( self.ap_cell, True )
730 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
731 self.plist.append_column( self.ap_col )
732 # The signal column
733 self.sig_cell = gtk.CellRendererPixbuf()
734 self.signal_col = gtk.TreeViewColumn( "Signal" )
735 self.signal_col.pack_start( self.sig_cell, True )
736 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
737 self.plist.append_column( self.signal_col )
738 # The mode column
739 self.mode_cell = gtk.CellRendererText()
740 self.mode_col = gtk.TreeViewColumn( "Mode" )
741 self.mode_col.pack_start( self.mode_cell, True )
742 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
743 self.plist.append_column( self.mode_col )
744 # The protocol column
745 self.prot_cell = gtk.CellRendererText()
746 self.protocol_col = gtk.TreeViewColumn( "802.11" )
747 self.protocol_col.pack_start( self.prot_cell, True )
748 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
749 self.plist.append_column( self.protocol_col )
750 # The channel column
751 self.channel_cell = gtk.CellRendererText()
752 self.channel_col = gtk.TreeViewColumn( "Channel" )
753 self.channel_col.pack_start( self.channel_cell, True )
754 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
755 self.plist.append_column( self.channel_col )
756 # DnD Ordering
757 self.plist.set_reorderable( True )
758 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
759 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
760 # enable/disable buttons based on the selected network
761 self.selected_network = self.plist.get_selection()
762 self.selected_network.connect( 'changed', self.on_network_selection, None )
763 # the list scroll bar
764 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
765 sb.show()
766 self.plist.show()
767 # Add New button
768 self.new_button = gtk.Button( "_New" )
769 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
770 self.new_button.show()
771 # Add Configure button
772 self.edit_button = gtk.Button( "C_onfigure" )
773 self.edit_button.connect( 'clicked', self.edit_profile, None )
774 self.edit_button.show()
775 self.edit_button.set_sensitive(False)
776 # Add Delete button
777 self.delete_button = gtk.Button( "_Delete" )
778 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
779 self.delete_button.show()
780 self.delete_button.set_sensitive(False)
781 # Add Connect button
782 self.connect_button = gtk.Button( "Co_nnect" )
783 self.connect_button.connect( 'clicked', self.connect_profile, None )
784 # Add Disconnect button
785 self.disconnect_button = gtk.Button( "D_isconnect" )
786 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
787 # lets add our widgets
788 rows = gtk.VBox( False, 3 )
789 net_list = gtk.HBox( False, 0 )
790 listcols = gtk.HBox( False, 0 )
791 prows = gtk.VBox( False, 0 )
792 # lets start packing
793 # the network list
794 net_list.pack_start( self.plist, True, True, 0 )
795 net_list.pack_start( sb, False, False, 0 )
796 # the rows level
797 rows.pack_start( net_list , True, True, 0 )
798 rows.pack_start( self.current_network, False, True, 0 )
799 # the list columns
800 listcols.pack_start( rows, True, True, 0 )
801 listcols.pack_start( prows, False, False, 5 )
802 # the list buttons
803 prows.pack_start( self.new_button, False, False, 2 )
804 prows.pack_start( self.edit_button, False, False, 2 )
805 prows.pack_start( self.delete_button, False, False, 2 )
806 prows.pack_end( self.connect_button, False, False, 2 )
807 prows.pack_end( self.disconnect_button, False, False, 2 )
809 self.window.action_area.pack_start( self.about_button )
810 self.window.action_area.pack_start( self.preferences_button )
811 self.window.action_area.pack_start( self.close_button )
813 rows.show()
814 prows.show()
815 listcols.show()
816 self.window.vbox.add( listcols )
817 self.window.vbox.set_spacing( 3 )
818 self.window.show_all()
820 # Now, immediately hide these two. The proper one will be
821 # displayed later, based on interface state. -BEF-
822 self.disconnect_button.hide()
823 self.connect_button.hide()
824 self.connect_button.set_sensitive(False)
826 # set up connection manager for later use
827 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
828 # set up status window for later use
829 self.status_window = StatusWindow( self )
830 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
832 # Add our known profiles in order
833 for ap in self.confFile.auto_profile_order:
834 ap = ap.strip()
835 self.access_points[ ap ] = self.confFile.get_profile( ap )
836 wep = None
837 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
838 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'] ] )
839 # This is the first run (or, at least, no config file was present), so pop up the preferences window
840 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
841 self.confFile.remove_option('DEFAULT', 'new_file')
842 self.edit_preferences(self.preferences_button)
844 # Begin running radar_window in Gtk event loop.
846 #Parameters:
848 # nothing
850 #Returns:
852 # nothing
853 def main( self ):
854 gtk.main()
856 # Quit application.
858 #Parameters:
860 # 'widget' -- gtk.Widget - The widget sending the event.
862 #Returns:
864 # nothing
865 def destroy( self, widget = None):
866 if self.status_window:
867 self.status_window.destroy()
868 gtk.main_quit()
870 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
872 #Parameters:
874 # 'widget' -- gtk.Widget - The widget sending the event.
876 # 'data' -- tuple - list of arbitrary arguments (not used)
878 #Returns:
880 # boolean -- always return False (i.e. do not propigate the signal which called)
881 def delete_event( self, widget, data = None ):
882 # Let other threads know it is time to exit
883 self.exit_event.set()
884 # Wait for all other threads to exit before continuing
885 while threading.activeCount() > 1:
886 sleep(0.25)
887 self.destroy()
888 return False
890 # Updates the on-screen profiles list.
892 #Parameters:
894 # nothing
896 #Returns:
898 # boolean -- always return True
899 def update_plist_items( self ):
900 # Indicate to PyGtk that only one Gtk thread should run here
901 gtk.gdk.threads_enter()
902 # update the current ip and essid
903 # set the state of connect/disconnect buttons based on whether we have an IP address
904 if self.connection:
905 if self.connection.state:
906 self.current_network.set_text( "Connected to %s\nIP Address %s" % ( make_section_name( self.connection.get_current_essid(), self.connection.get_current_bssid() ), self.connection.get_current_ip() ) )
907 self.connect_button.hide()
908 self.disconnect_button.show()
909 else:
910 self.current_network.set_text( "Not Connected." )
911 self.disconnect_button.hide()
912 self.connect_button.show()
914 while True:
915 # Get profiles scanned by iwlist
916 try:
917 ap = self.apQueue.get_nowait()
918 except Queue.Empty:
919 break
920 else:
921 profile = self.confFile.get_profile(make_section_name(ap['essid'], ap['bssid']))
922 if not profile:
923 profile = self.confFile.get_profile(make_section_name(ap['essid'], ''))
924 if not profile:
925 profile = get_new_profile()
926 if profile['roaming']:
927 prow_iter = self.get_row_by_ap(ap['essid'], ' Multiple APs')
928 else:
929 prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
930 wep = None
931 if prow_iter != None:
932 # the AP is in the list of APs on the screen
933 apname = make_section_name(ap['essid'], ap['bssid'])
934 if self.access_points.has_key(apname):
935 # This AP has been configured and is/should be stored in the config file
936 ap['known'] = self.access_points[apname]['known']
937 self.access_points[apname]['available'] = ap['available']
938 self.access_points[apname]['encrypted'] = ap['encrypted']
939 self.access_points[apname]['signal'] = ap['signal']
940 self.access_points[apname]['mode'] = ap['mode']
941 self.access_points[apname]['protocol'] = ap['protocol']
942 self.access_points[apname]['channel'] = ap['channel']
943 # Set the 'known' values; False is default, overridden to True by self.access_points
944 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
945 self.pstore.set_value(prow_iter, 2, ap['known'])
946 self.pstore.set_value(prow_iter, 3, ap['available'])
947 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
948 self.pstore.set_value(prow_iter, 4, wep)
949 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(ap['signal']))
950 self.pstore.set_value(prow_iter, 6, ap['mode'])
951 self.pstore.set_value(prow_iter, 7, ap['protocol'])
952 self.pstore.set_value(prow_iter, 8, ap['channel'])
953 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
954 #for val in self.pstore[prow_iter]:
955 #print val,
956 else:
957 # the AP is not in the list of APs on the screen
958 if profile['roaming']:
959 ap_name = ap['essid'] + "\n" + ' Multiple APs'
960 else:
961 ap_name = ap['essid'] + "\n" + ap['bssid']
962 self.pstore.append([ap_name, self.pixbuf_from_known(ap['known']), ap['known'], ap['available'], wep, self.pixbuf_from_signal(ap['signal']), ap['mode'], ap['protocol'], ap['channel']])
963 #print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']
964 # Allow other Gtk threads to run
965 gtk.gdk.threads_leave()
966 #print "update_plist_items: Empty apQueue"
967 return True
969 # Return the proper icon for a value of known.
971 #Parameters:
973 # 'known' -- boolean - Whether the AP is known (i.e. configured)
975 #Returns:
977 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
978 def pixbuf_from_known( self, known ):
979 """ return the proper icon for value of known """
980 if known:
981 return self.known_profile_icon
982 else:
983 return self.unknown_profile_icon
985 # Return an icon indicating the signal level.
987 #Parameters:
989 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
991 #Returns:
993 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
994 def pixbuf_from_signal( self, signal ):
995 signal = int( signal )
996 # shift signal up by 80 to convert dBm scale to arbitrary scale
997 if signal < 0: signal = signal + 80
998 #print "signal level:", signal
999 if signal < 3:
1000 return self.signal_none_pb
1001 elif signal < 12:
1002 return self.signal_low_pb
1003 elif signal < 20:
1004 return self.signal_barely_pb
1005 elif signal < 35:
1006 return self.signal_ok_pb
1007 elif signal >= 35:
1008 return self.signal_best_pb
1009 else:
1010 return None
1012 # Return row which holds specified ESSID and BSSID.
1014 #Parameters:
1016 # 'essid' -- string - ESSID to match
1018 # 'bssid' -- string - BSSID to match
1020 #Returns:
1022 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1023 def get_row_by_ap( self, essid, bssid ):
1024 for row in self.pstore:
1025 if ( row[0] == essid + "\n" + bssid ):
1026 #print "matched:", row.iter, essid, bssid
1027 return row.iter
1028 return None
1030 # Enable/disable buttons based on the selected network.
1032 #Parameters:
1034 # 'widget' -- gtk.Widget - The widget sending the event.
1036 # 'data' -- tuple - list of arbitrary arguments (not used)
1038 #Returns:
1040 # nothing
1041 def on_network_selection( self, widget, data = None ):
1042 ( store, selected_iter ) = self.selected_network.get_selected()
1043 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1044 # if no networks are selected, disable all buttons except New
1045 # (this occurs after a drag-and-drop)
1046 if selected_iter == None:
1047 self.edit_button.set_sensitive(False)
1048 self.delete_button.set_sensitive(False)
1049 self.connect_button.set_sensitive(False)
1050 return
1051 # enable/disable buttons
1052 self.connect_button.set_sensitive(True)
1053 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1054 self.edit_button.set_sensitive(True)
1055 self.delete_button.set_sensitive(True)
1056 else:
1057 self.edit_button.set_sensitive(True)
1058 self.delete_button.set_sensitive(False)
1060 # Init and run the about dialog
1062 #Parameters:
1064 # 'widget' -- gtk.Widget - The widget sending the event.
1066 # 'data' -- tuple - list of arbitrary arguments (not used)
1068 #Returns:
1070 # nothing
1071 def show_about_info( self, widget, data=None ):
1072 about = about_dialog()
1073 about.run()
1074 about.destroy()
1076 # Init and run the preferences dialog
1078 #Parameters:
1080 # 'widget' -- gtk.Widget - The widget sending the event.
1082 # 'data' -- tuple - list of arbitrary arguments (not used)
1084 #Returns:
1086 # nothing
1087 def edit_preferences( self, widget, data=None ):
1088 # get raw strings from config file
1089 self.confFile.raw = True
1090 prefs = preferences_dialog( self, self.confFile )
1091 response = prefs.run()
1092 prefs.destroy()
1093 if response == int(gtk.RESPONSE_ACCEPT):
1094 prefs.save()
1095 # get cooked strings from config file
1096 self.confFile.raw = False
1098 # Respond to a request to create a new AP profile
1100 #Parameters:
1102 # 'widget' -- gtk.Widget - The widget sending the event.
1104 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1106 # 'data' -- tuple - list of arbitrary arguments (not used)
1108 #Returns:
1110 # boolean -- True if a profile was created and False if profile creation was canceled.
1111 def create_new_profile( self, widget, profile, data=None ):
1112 profile_editor = profile_dialog( self, profile )
1113 try:
1114 profile = profile_editor.run()
1115 except ValueError:
1116 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1117 del error_dlg
1118 return False
1119 finally:
1120 profile_editor.destroy()
1121 if profile:
1122 apname = make_section_name( profile['essid'], profile['bssid'] )
1123 # Check that the ap does not exist already
1124 if apname in self.confFile.profiles():
1125 error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
1126 del error_dlg
1127 # try again
1128 self.access_points[ apname ] = profile
1129 self.confFile.set_section( apname, profile )
1130 # if it is not in the auto_profile_order add it
1131 if apname not in self.confFile.auto_profile_order:
1132 self.confFile.auto_profile_order.append(apname)
1133 # add to the store
1134 wep = None
1135 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1136 try:
1137 self.confFile.write()
1138 except IOError, (error_number, error_str):
1139 if error_number == errno.ENOENT:
1140 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1141 del error_dlg
1142 else:
1143 raise IOError(error_number, error_str)
1144 # Add AP to the list displayed to user
1145 try:
1146 self.commandQueue.put('pause')
1147 self.apQueue.put_nowait( self.access_points[ apname ] )
1148 self.commandQueue.put('scan')
1149 except Queue.Full:
1150 pass
1151 return True
1152 else:
1153 # Did not create new profile
1154 return False
1156 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1157 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1159 #Parameters:
1161 # 'widget' -- gtk.Widget - The widget sending the event.
1163 # 'data' -- tuple - list of arbitrary arguments (not used)
1165 #Returns:
1167 # nothing
1168 def edit_profile( self, widget, data=None ):
1169 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1170 if not selected_iter: return
1171 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1172 if row_start[1] == ' Multiple APs':
1173 apname = make_section_name(row_start[0], '')
1174 else:
1175 apname = make_section_name(row_start[0], row_start[1])
1176 profile = self.confFile.get_profile( apname )
1177 if profile:
1178 profile_editor = profile_dialog( self, profile )
1179 try:
1180 edited_profile = profile_editor.run()
1181 except ValueError:
1182 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1183 del error_dlg
1184 return False
1185 finally:
1186 profile_editor.destroy()
1187 if edited_profile:
1188 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
1189 self.delete_profile(selected_iter, apname)
1190 apname = make_section_name(profile['essid'], profile['bssid'])
1191 del self.access_points[apname]
1192 self.confFile.remove_section(apname)
1193 # Add AP to the list displayed to user
1194 try:
1195 self.commandQueue.put('pause')
1196 self.apQueue.put_nowait(edited_profile)
1197 self.commandQueue.put('scan')
1198 except Queue.Full:
1199 pass
1200 apname = make_section_name(edited_profile['essid'], edited_profile['bssid'])
1201 self.access_points[apname] = edited_profile
1202 self.confFile.set_section(apname, edited_profile)
1203 try:
1204 self.confFile.write()
1205 except IOError, (error_number, error_str):
1206 if error_number == errno.ENOENT:
1207 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1208 del error_dlg
1209 else:
1210 raise IOError(error_number, error_str)
1211 else:
1212 profile = get_new_profile()
1213 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1214 self.create_new_profile( widget, profile, data )
1216 # Delete an AP profile (i.e. make profile unknown)
1218 #Parameters:
1220 # 'selected_iter' -- gtk.TreeIter - The selected row.
1222 # 'apname' -- string - The configuration file section to remove
1224 #Returns:
1226 # nothing
1227 def delete_profile(self, selected_iter, apname):
1228 # Remove it
1229 del self.access_points[apname]
1230 - self.confFile.remove_section(apname)
1231 self.logger.info(apname)
1232 if apname in self.confFile.auto_profile_order:
1233 self.confFile.auto_profile_order.remove(apname)
1234 self.pstore.remove(selected_iter)
1235 # Let's save our current state
1236 self.update_auto_profile_order()
1237 try:
1238 self.confFile.write()
1239 except IOError, (error_number, error_str):
1240 if error_number == errno.ENOENT:
1241 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1242 del error_dlg
1243 else:
1244 raise IOError(error_number, error_str)
1246 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1247 # Check with user first.
1249 #Parameters:
1251 # 'widget' -- gtk.Widget - The widget sending the event.
1253 # 'data' -- tuple - list of arbitrary arguments (not used)
1255 #Returns:
1257 # nothing
1258 def delete_profile_with_check(self, widget, data=None):
1259 (store, selected_iter) = self.plist.get_selection().get_selected()
1260 if not selected_iter: return
1261 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
1262 known = store.get_value( selected_iter, 1 )
1263 if not known: return
1264 res = dlg.run()
1265 dlg.destroy()
1266 del dlg
1267 if res == gtk.RESPONSE_NO:
1268 return
1269 self.delete_profile(selected_iter, apname)
1271 # Respond to a request to connect to an AP.
1273 #Parameters:
1275 # 'widget' -- gtk.Widget - The widget sending the event.
1277 # 'profile' -- dictionary - The AP profile to which to connect.
1279 # 'data' -- tuple - list of arbitrary arguments (not used)
1281 #Returns:
1283 # nothing
1284 def connect_profile( self, widget, profile, data=None ):
1285 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1286 if not selected_iter: return
1287 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1288 known = store.get_value( selected_iter, 2 )
1289 if not known:
1290 if data != 'noconnect':
1291 dlg = gtk.MessageDialog(
1292 self.window,
1293 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1294 gtk.MESSAGE_QUESTION,
1295 gtk.BUTTONS_YES_NO,
1296 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1297 res = dlg.run()
1298 dlg.destroy()
1299 del dlg
1300 if res == gtk.RESPONSE_NO: return
1301 profile = get_new_profile()
1302 profile['essid'] = essid
1303 profile['bssid'] = bssid
1304 if not self.create_new_profile( widget, profile, data ):
1305 return
1306 apname = make_section_name( essid, bssid )
1307 self.connection.connect_to_network(self.access_points[apname], self.status_window)
1309 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1311 #Parameters:
1313 # 'widget' -- gtk.Widget - The widget sending the event.
1315 # 'data' -- tuple - list of arbitrary arguments (not used)
1317 #Returns:
1319 # nothing
1320 def disconnect_profile( self, widget, data=None ):
1321 if data == "cancel":
1322 self.status_window.update_message("Canceling connection...")
1323 if sys.modules.has_key("gtk"):
1324 while gtk.events_pending():
1325 gtk.main_iteration(False)
1326 sleep(1)
1327 self.connection.disconnect_interface()
1329 # Update the config file auto profile order from the on-screen order
1331 #Parameters:
1333 # 'widget' -- gtk.Widget - The widget sending the event.
1335 # 'data' -- tuple - list of arbitrary arguments (not used)
1337 # 'data2' -- tuple - list of arbitrary arguments (not used)
1339 #Returns:
1341 # nothing
1342 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1343 # recreate the auto_profile_order
1344 auto_profile_order = []
1345 piter = self.pstore.get_iter_first()
1346 while piter:
1347 # only if it's known
1348 if self.pstore.get_value( piter, 2 ) == True:
1349 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1350 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1351 piter = self.pstore.iter_next( piter )
1352 self.confFile.auto_profile_order = auto_profile_order
1353 try:
1354 self.confFile.write()
1355 except IOError, (error_number, error_str):
1356 if error_number == errno.ENOENT:
1357 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1358 del error_dlg
1359 else:
1360 raise IOError(error_number, error_str)
1363 # Button to allow user to choose a file and put value into specified gtk.Entry
1364 class file_browse_button(gtk.Button):
1365 # Create a button to simulate a File/Open
1367 #Parameters:
1369 # 'parent' -- gtk.Object -- Usually, the calling window.
1371 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1373 #Returns:
1375 # file_browse_button instance
1376 def __init__( self, parent, entry ):
1377 self.parent_window = parent
1378 self.entry = entry
1379 gtk.Button.__init__(self, "Browse", None)
1380 #self.
1381 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)
1382 self.connect("clicked", self.browse_files)
1384 # Show filechooser dialog and get user selection
1386 #Parameters:
1388 # 'widget' -- gtk.Widget -- The widget sending the event.
1390 #Returns:
1392 # nothing
1394 #NOTES:
1396 # updates entry value
1398 def browse_files( self, widget ):
1399 self.browser_dialog.set_filename(self.entry.get_text())
1400 self.browser_dialog.run()
1401 self.entry.set_text(self.browser_dialog.get_filename())
1402 self.browser_dialog.destroy()
1405 # Simple dialog to report an error to the user.
1406 class ErrorDialog:
1407 # Create a new ErrorDialog.
1409 #Parameters:
1411 # 'parent' -- gtk.Object - Usually, the calling window.
1413 # 'message' -- string - The message to display to the user.
1415 #Returns:
1417 # ErrorDialog instance
1418 def __init__( self, parent, message ):
1419 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1420 dialog.run()
1421 dialog.destroy()
1422 del dialog
1425 # The preferences dialog. Edits non-profile sections of the config file.
1426 class preferences_dialog:
1427 # Create a new preferences_dialog.
1429 #Parameters:
1431 # 'parent' -- gtk.Object - Usually, the calling window.
1433 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1435 #Returns:
1437 # preferences_dialog instance
1438 def __init__( self, parent, confFile ):
1439 global wifi_radar_icon
1440 self.parent = parent
1441 self.confFile = confFile
1442 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1443 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1444 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1445 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1446 self.dialog.set_icon( icon )
1447 self.dialog.set_resizable( True )
1448 self.dialog.set_transient_for( self.parent.window )
1449 self.tooltips = gtk.Tooltips()
1451 # set up preferences widgets
1453 # build everything in a tabbed notebook
1454 self.prefs_notebook = gtk.Notebook()
1456 ### General tab
1457 self.general_page = gtk.VBox()
1458 # auto detect wireless device
1459 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1461 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1463 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1464 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1465 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1466 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1468 # network interface selecter
1469 self.w_interface = gtk.combo_box_entry_new_text()
1470 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1471 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1472 for device in wireless_devices:
1473 if device != self.confFile.get_opt('DEFAULT.interface'):
1474 self.w_interface.append_text(device)
1475 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1476 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1477 self.w_interface.set_active(0)
1478 self.w_interface_label = gtk.Label("Wireless device")
1479 self.w_hbox1 = gtk.HBox(False, 0)
1480 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1481 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1482 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1483 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1485 # scan timeout (spin button of integers from 1 to 100)
1486 #self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1487 #self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1488 #self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1489 #self.w_scan_timeout.set_numeric(True)
1490 #self.w_scan_timeout.set_snap_to_ticks(True)
1491 #self.w_scan_timeout.set_wrap(False)
1492 #self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1493 #self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1494 #self.w_hbox2 = gtk.HBox(False, 0)
1495 #self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1496 #self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1497 #self.general_page.pack_start(self.w_hbox2, False, False, 5)
1499 # speak up
1500 self.w_speak_up = gtk.CheckButton("Use speak-up")
1501 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1502 self.w_speak_up.connect("toggled", self.toggle_speak)
1503 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1504 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1506 # speak up command
1507 self.w_speak_cmd = gtk.Entry()
1508 self.w_speak_cmd.set_width_chars(16)
1509 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1510 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1511 self.w_speak_cmd_label = gtk.Label("Speak Command")
1512 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1513 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1514 self.w_hbox3 = gtk.HBox(False, 0)
1515 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1516 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1517 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1518 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1519 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1521 # commit required
1522 self.w_commit_required = gtk.CheckButton("Commit required")
1523 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1524 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1525 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1527 # ifup required
1528 self.w_ifup_required = gtk.CheckButton("Ifup required")
1529 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1530 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1531 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1533 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1534 ### End of General tab
1536 ### Advanced tab
1537 # table to use for layout of following command configurations
1538 self.cmds_table = gtk.Table()
1540 # ifconfig command
1541 self.ifconfig_cmd = gtk.Entry()
1542 self.ifconfig_cmd.set_width_chars(32)
1543 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1544 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1545 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1546 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1547 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1548 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1549 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1550 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1552 # iwconfig command
1553 self.iwconfig_cmd = gtk.Entry()
1554 self.iwconfig_cmd.set_width_chars(32)
1555 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1556 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1557 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1558 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1559 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1560 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1561 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1562 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1564 # iwlist command
1565 self.iwlist_cmd = gtk.Entry()
1566 self.iwlist_cmd.set_width_chars(32)
1567 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1568 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1569 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1570 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1571 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1572 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1573 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1574 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1576 # route command
1577 self.route_cmd = gtk.Entry()
1578 self.route_cmd.set_width_chars(32)
1579 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1580 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1581 self.route_cmd_label = gtk.Label("Network route configure command")
1582 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1583 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1584 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1585 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1586 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1588 # log file
1589 self.logfile_entry = gtk.Entry()
1590 self.logfile_entry.set_width_chars(32)
1591 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1592 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1593 self.logfile_label = gtk.Label("Log file")
1594 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1595 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1596 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, True, False, 5, 0)
1597 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, True, False, 0, 0)
1598 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, False, False, 0, 0)
1600 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1601 ### End of Advanced tab
1603 ### DHCP tab
1604 # table to use for layout of DHCP prefs
1605 self.dhcp_table = gtk.Table()
1607 self.dhcp_cmd = gtk.Entry()
1608 self.dhcp_cmd.set_width_chars(32)
1609 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1610 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1611 self.dhcp_cmd_label = gtk.Label("Command")
1612 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1613 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1614 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1615 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1617 self.dhcp_args = gtk.Entry()
1618 self.dhcp_args.set_width_chars(32)
1619 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1620 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1621 self.dhcp_args_label = gtk.Label("Arguments")
1622 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1623 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1625 self.dhcp_kill_args = gtk.Entry()
1626 self.dhcp_kill_args.set_width_chars(32)
1627 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1628 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1629 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1630 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1631 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1633 self.dhcp_timeout = gtk.Entry()
1634 self.dhcp_timeout.set_width_chars(32)
1635 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1636 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1637 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1638 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1639 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1641 self.dhcp_pidfile = gtk.Entry()
1642 self.dhcp_pidfile.set_width_chars(32)
1643 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1644 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1645 self.dhcp_pidfile_label = gtk.Label("PID file")
1646 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1647 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1649 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1650 ### End of DHCP tab
1652 ### WPA tab
1653 # table to use for layout of DHCP prefs
1654 self.wpa_table = gtk.Table()
1656 self.wpa_cmd = gtk.Entry()
1657 self.wpa_cmd.set_width_chars(32)
1658 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1659 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1660 self.wpa_cmd_label = gtk.Label("Command")
1661 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1662 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1663 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1664 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1666 self.wpa_args = gtk.Entry()
1667 self.wpa_args.set_width_chars(32)
1668 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1669 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1670 self.wpa_args_label = gtk.Label("Arguments")
1671 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1672 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1674 self.wpa_kill_args = gtk.Entry()
1675 self.wpa_kill_args.set_width_chars(32)
1676 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1677 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1678 self.wpa_kill_args_label = gtk.Label("Kill command")
1679 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1680 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1682 self.wpa_config = gtk.Entry()
1683 self.wpa_config.set_width_chars(32)
1684 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1685 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1686 self.wpa_config_label = gtk.Label("Configuration file")
1687 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1688 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1690 self.wpa_driver = gtk.Entry()
1691 self.wpa_driver.set_width_chars(32)
1692 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1693 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1694 self.wpa_driver_label = gtk.Label("Driver")
1695 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1696 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1698 self.wpa_pidfile = gtk.Entry()
1699 self.wpa_pidfile.set_width_chars(32)
1700 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1701 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1702 self.wpa_pidfile_label = gtk.Label("PID file")
1703 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1704 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1706 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1707 ### End of WPA tab
1709 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1711 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1713 #Parameters:
1715 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1717 # 'data' -- tuple - list of arbitrary arguments (not used)
1719 #Returns:
1721 # nothing
1722 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1723 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1725 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1727 #Parameters:
1729 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1731 # 'data' -- tuple - list of arbitrary arguments (not used)
1733 #Returns:
1735 # nothing
1736 def toggle_speak(self, speak_toggle, data=None):
1737 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1738 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1740 # Display preferences dialog and operate until canceled or okayed.
1742 #Parameters:
1744 # nothing
1746 #Returns:
1748 # integer -- gtk response ID
1749 def run(self):
1750 self.dialog.show_all()
1751 return self.dialog.run()
1753 # Write updated values to config file.
1755 #Parameters:
1757 # nothing
1759 #Returns:
1761 # nothing
1762 def save(self):
1763 if self.w_auto_detect.get_active():
1764 set_network_device("auto_detect")
1765 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1766 else:
1767 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1768 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1769 self.confFile.set_opt('DEFAULT.interface', interface)
1770 set_network_device(interface)
1771 #self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1772 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1773 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1774 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1775 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1776 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1777 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1778 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1779 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1780 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1781 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1782 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1783 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1784 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1785 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1786 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1787 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1788 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1789 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1790 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1791 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1792 try:
1793 self.confFile.write()
1794 except IOError, (error_number, error_str):
1795 if error_number == errno.ENOENT:
1796 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1797 del error_dlg
1798 else:
1799 raise IOError(error_number, error_str)
1801 # Remove preferences window.
1803 #Parameters:
1805 # nothing
1807 #Returns:
1809 # nothing
1810 def destroy(self):
1811 self.dialog.destroy()
1812 del self.dialog
1815 # Edit and return an AP profile.
1816 class profile_dialog:
1817 # Create a new profile_dialog.
1819 #Parameters:
1821 # 'parent' -- gtk.Object - Usually, the calling window.
1823 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1825 #Returns:
1827 # profile_dialog instance
1828 def __init__( self, parent, profile ):
1829 global wifi_radar_icon
1831 # Labels
1832 WIFI_SET_LABEL = "WiFi Options"
1833 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
1834 USE_IP_LABEL = "Manual network configuration"
1835 USE_WPA_LABEL = "Use WPA"
1836 NO_WPA_LABEL = "No WPA"
1837 CON_PP_LABEL = "Connection Commands"
1838 DIS_PP_LABEL = "Disconnection Commands"
1840 self.parent = parent
1841 self.profile = profile.copy()
1842 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1843 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1844 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1845 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1846 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1847 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1848 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1849 self.dialog.set_icon( icon )
1850 self.dialog.set_resizable( False )
1851 self.dialog.set_transient_for( self.parent.window )
1852 #self.dialog.set_size_request( 400, 400 )
1853 #################
1854 self.tooltips = gtk.Tooltips()
1856 general_table = gtk.Table()
1857 general_table.set_row_spacings(3)
1858 general_table.set_col_spacings(3)
1859 # The essid labels
1860 general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
1861 # The essid textboxes
1862 self.essid_entry = gtk.Entry(32)
1863 self.essid_entry.set_text(self.profile['essid'])
1864 general_table.attach(self.essid_entry, 1, 2, 0, 1)
1865 # Add the essid table to the dialog
1866 self.dialog.vbox.pack_start(general_table, True, True, 5)
1867 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
1869 # The bssid labels
1870 general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
1871 # The bssid textboxes
1872 self.bssid_entry = gtk.Entry(32)
1873 self.bssid_entry.set_text(self.profile['bssid'])
1874 self.bssid_entry.set_sensitive(not self.profile['roaming'])
1875 general_table.attach(self.bssid_entry, 1, 2, 1, 2)
1876 # Add the bssid table to the dialog
1877 self.dialog.vbox.pack_start(general_table, True, True, 5)
1878 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
1879 # Add the roaming checkbox
1880 self.roaming_cb = gtk.CheckButton('Roaming')
1881 self.roaming_cb.set_active(self.profile['roaming'])
1882 general_table.attach(self.roaming_cb, 1, 2, 1, 2)
1883 self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
1884 # create the WiFi expander
1885 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1886 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1887 wifi_table = gtk.Table( 4, 2, False )
1888 wifi_table.set_row_spacings( 3 )
1889 wifi_table.set_col_spacings( 3 )
1890 # The WiFi labels
1891 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1892 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1893 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1894 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1895 # The WiFi text boxes
1896 self.mode_combo = gtk.combo_box_new_text()
1897 for mode in self.WIFI_MODES:
1898 self.mode_combo.append_text( mode )
1899 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1900 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1901 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
1902 self.channel_combo = gtk.combo_box_new_text()
1903 for channel in self.WIFI_CHANNELS:
1904 self.channel_combo.append_text( channel )
1905 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1906 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1907 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
1909 self.key_entry = gtk.Entry( 64 )
1910 self.key_entry.set_text( self.profile['key'] )
1911 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1912 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
1914 self.security_combo = gtk.combo_box_new_text()
1915 for security in self.WIFI_SECURITY:
1916 self.security_combo.append_text( security )
1917 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1918 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1919 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
1920 # Add the wifi table to the expander
1921 self.wifi_expander.add( wifi_table )
1922 # Add the expander to the dialog
1923 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1925 # create the wpa expander
1926 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1927 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1928 wpa_table = gtk.Table( 1, 2, False )
1929 wpa_table.set_row_spacings( 3 )
1930 wpa_table.set_col_spacings( 3 )
1931 # The labels
1932 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1933 # The text boxes
1934 self.wpa_driver_entry = gtk.Entry()
1935 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1936 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1937 # Add the wpa table to the expander
1938 self.wpa_expander.add( wpa_table )
1939 # Add the expander to the dialog
1940 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1942 # create the dhcp expander
1943 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1944 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1945 ip_table = gtk.Table( 6, 2, False )
1946 ip_table.set_row_spacings( 3 )
1947 ip_table.set_col_spacings( 3 )
1948 # The IP labels
1949 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1950 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1951 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1952 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1953 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1954 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1955 # The IP text boxes
1956 self.ip_entry = gtk.Entry( 15 )
1957 self.ip_entry.set_text( self.profile['ip'] )
1958 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1959 self.netmask_entry = gtk.Entry( 15 )
1960 self.netmask_entry.set_text( self.profile['netmask'] )
1961 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1962 self.gw_entry = gtk.Entry( 15 )
1963 self.gw_entry.set_text( self.profile['gateway'] )
1964 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1965 self.domain_entry = gtk.Entry( 32 )
1966 self.domain_entry.set_text( self.profile['domain'] )
1967 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1968 self.dns1_entry = gtk.Entry( 15 )
1969 self.dns1_entry.set_text( self.profile['dns1'] )
1970 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1971 self.dns2_entry = gtk.Entry( 15 )
1972 self.dns2_entry.set_text( self.profile['dns2'] )
1973 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1974 # Add the ip table to the expander
1975 self.dhcp_expander.add( ip_table )
1976 # Add the expander to the dialog
1977 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1979 # create the connection-building postpre expander
1980 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1981 con_pp_table = gtk.Table( 2, 2, False )
1982 con_pp_table.set_row_spacings( 3 )
1983 con_pp_table.set_col_spacings( 3 )
1984 # The labels
1985 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1986 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1987 # The text boxes
1988 self.con_prescript_entry = gtk.Entry()
1989 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1990 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1991 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
1992 self.con_postscript_entry = gtk.Entry()
1993 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1994 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1995 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
1996 # Add the pp table to the expander
1997 self.con_pp_expander.add( con_pp_table )
1998 # Add the expander to the dialog
1999 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
2001 # create the disconnection postpre expander
2002 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
2003 dis_pp_table = gtk.Table( 2, 2, False )
2004 dis_pp_table.set_row_spacings( 3 )
2005 dis_pp_table.set_col_spacings( 3 )
2006 # The labels
2007 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2008 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2009 # The text boxes
2010 self.dis_prescript_entry = gtk.Entry()
2011 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
2012 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
2013 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
2014 self.dis_postscript_entry = gtk.Entry()
2015 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
2016 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
2017 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
2018 # Add the pp table to the expander
2019 self.dis_pp_expander.add( dis_pp_table )
2020 # Add the expander to the dialog
2021 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
2023 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
2025 #Parameters:
2027 # nothing
2029 #Returns:
2031 # dictionary or None -- a profile, or None on cancel
2033 #NOTES:
2035 # Raises ValueError if an attempt is made to save an ESSID with no name.
2036 def run( self ):
2037 self.dialog.show_all()
2038 if self.dialog.run():
2039 if self.essid_entry.get_text().strip() == "":
2040 raise ValueError
2041 self.profile['known'] = True
2042 self.profile['essid'] = self.essid_entry.get_text().strip()
2043 self.profile['bssid'] = self.bssid_entry.get_text().strip()
2044 self.profile['roaming'] = self.roaming_cb.get_active()
2045 self.profile['key'] = self.key_entry.get_text().strip()
2046 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
2047 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
2048 self.profile['encrypted'] = ( self.profile['security'] != '' )
2049 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
2050 self.profile['protocol'] = 'g'
2051 self.profile['available'] = ( self.profile['signal'] > 0 )
2052 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
2053 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
2054 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
2055 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
2056 # wpa
2057 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
2058 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
2059 # dhcp
2060 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
2061 self.profile['ip'] = self.ip_entry.get_text().strip()
2062 self.profile['netmask'] = self.netmask_entry.get_text().strip()
2063 self.profile['gateway'] = self.gw_entry.get_text().strip()
2064 self.profile['domain'] = self.domain_entry.get_text().strip()
2065 self.profile['dns1'] = self.dns1_entry.get_text().strip()
2066 self.profile['dns2'] = self.dns2_entry.get_text().strip()
2067 return self.profile
2068 return None
2070 # Remove profile dialog.
2072 #Parameters:
2074 # nothing
2076 #Returns:
2078 # nothing
2079 def destroy( self ):
2080 self.dialog.destroy()
2081 del self.dialog
2083 # Respond to expanding/hiding IP segment.
2085 #Parameters:
2087 # 'widget' -- gtk.Widget - The widget sending the event.
2089 # 'data' -- tuple - List of arbitrary arguments (not used)
2091 #Returns:
2093 # nothing
2094 def toggle_use_dhcp( self, widget, data = None ):
2095 expanded = self.dhcp_expander.get_expanded()
2096 if expanded:
2097 self.dhcp_expander.set_label( USE_IP_LABEL )
2098 else:
2099 self.dhcp_expander.set_label( USE_DHCP_LABEL )
2101 # Respond to expanding/hiding WPA segment.
2103 #Parameters:
2105 # 'widget' -- gtk.Widget - The widget sending the event.
2107 # 'data' -- tuple - List of arbitrary arguments (not used)
2109 #Returns:
2111 # nothing
2112 def toggle_use_wpa( self, widget, data = None ):
2113 expanded = self.wpa_expander.get_expanded()
2114 if expanded:
2115 self.wpa_expander.set_label( USE_WPA_LABEL )
2116 else:
2117 self.wpa_expander.set_label( NO_WPA_LABEL )
2119 # Return the index where item matches a cell in array.
2121 #Parameters:
2123 # 'item' -- string - Item to find in array
2125 # 'array' -- list - List in which to find match.
2127 #Returns:
2129 # integer - 0 (no match) or higher (index of match)
2130 def get_array_index( self, item, array ):
2131 try:
2132 return array.index( item.strip() )
2133 except:
2134 pass
2135 return 0
2137 # Return the value in array[ index ]
2139 #Parameters:
2141 # 'index' -- integer - The index to look up.
2143 # 'array' -- list - List in which to look up value.
2145 #Returns:
2147 # string -- empty string (no match) or looked up value
2148 def get_array_item( self, index, array ):
2149 try:
2150 return array[ index ]
2151 except:
2152 pass
2153 return ''
2156 # A simple class for putting up a "Please wait" dialog so the user
2157 # doesn't think we've forgotten about them. Implements the status interface.
2158 class StatusWindow:
2159 # Create a new StatusWindow.
2161 #Parameters:
2163 # 'parent' -- gtk.Object - Usually, the calling window.
2165 #Returns:
2167 # StatusWindow instance
2169 #NOTE:
2171 # Sample implementation of status interface. Status interface
2172 #requires .show(), .update_message(message), and .hide() methods.
2173 def __init__( self, parent ):
2174 global wifi_radar_icon
2175 self.parent = parent
2176 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2177 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2178 self.dialog.set_icon( icon )
2179 self.lbl = gtk.Label("Please wait...")
2180 self.bar = gtk.ProgressBar()
2181 self.dialog.vbox.pack_start(self.lbl)
2182 self.dialog.vbox.pack_start(self.bar)
2183 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2184 self.timer = None
2186 # Change the message displayed to the user.
2188 #Parameters:
2190 # 'message' -- string - The message to show to the user.
2192 #Returns:
2194 # nothing
2195 def update_message( self, message ):
2196 self.lbl.set_text(message)
2198 # Update the StatusWindow progress bar.
2200 #Parameters:
2202 # nothing
2204 #Returns:
2206 # True -- always return True
2207 def update_window( self ):
2208 self.bar.pulse()
2209 return True
2211 # Display and operate the StatusWindow.
2213 #Parameters:
2215 # nothing
2217 #Returns:
2219 # nothing
2220 def run( self ):
2221 pass
2223 # Show all the widgets of the StatusWindow.
2225 #Parameters:
2227 # nothing
2229 #Returns:
2231 # nothing
2232 def show( self ):
2233 self.dialog.show_all()
2234 self.timer = gobject.timeout_add(250, self.update_window)
2235 return False
2237 # Hide all the widgets of the StatusWindow.
2239 #Parameters:
2241 # nothing
2243 #Returns:
2245 # nothing
2246 def hide( self ):
2247 if self.timer:
2248 gobject.source_remove(self.timer)
2249 self.timer = None
2250 self.dialog.hide_all()
2251 return False
2253 # Remove the StatusWindow.
2255 #Parameters:
2257 # nothing
2259 #Returns:
2261 # nothing
2262 def destroy( self ):
2263 if self.timer:
2264 gobject.source_remove(self.timer)
2265 self.dialog.destroy()
2266 del self.dialog
2269 # Manage a GTK About Dialog
2270 class about_dialog(gtk.AboutDialog):
2271 # Subclass GTK AboutDialog
2273 #Parameters:
2275 # nothing
2277 #Returns:
2279 # nothing
2280 def __init__( self ):
2281 global wifi_radar_icon
2283 gtk.AboutDialog.__init__(self)
2284 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"])
2285 self.set_comments("WiFi connection manager")
2286 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2287 self.set_documenters(["Gary Case"])
2288 license = """
2289 This program is free software; you can redistribute it and/or modify
2290 it under the terms of the GNU General Public License as published by
2291 the Free Software Foundation; either version 2 of the License, or
2292 (at your option) any later version.
2294 This program is distributed in the hope that it will be useful,
2295 but WITHOUT ANY WARRANTY; without even the implied warranty of
2296 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2297 GNU General Public License for more details.
2299 You should have received a copy of the GNU General Public License
2300 along with this program; if not, write to the Free Software
2301 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2302 self.set_license(license)
2303 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2304 self.set_logo(logo)
2305 self.set_name("WiFi Radar")
2306 self.set_version(WIFI_RADAR_VERSION)
2307 self.set_website("http://wifi-radar.berlios.de")
2311 # Manage the configuration for the application, including reading and writing the config from/to a file.
2312 class ConfigFile(ConfigParser.SafeConfigParser):
2313 # Create a new ConfigFile.
2315 #Parameters:
2317 # 'filename' -- string - The configuration file's name.
2319 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2321 #Returns:
2323 # ConfigFile instance
2324 def __init__( self, filename, defaults, raw=False ):
2325 self.filename = filename
2326 self.raw = raw
2327 self.auto_profile_order = []
2328 ConfigParser.SafeConfigParser.__init__(self, defaults)
2330 # Set the contents of a section to values from a dictionary.
2332 #Parameters:
2334 # 'section_name' -- string - Configuration file section.
2336 # 'section_dict' -- dictionary - Values to add to section.
2338 #Returns:
2340 # nothing
2341 def set_section( self, section_name, section_dict ):
2342 try:
2343 self.add_section(section_name)
2344 except ConfigParser.DuplicateSectionError:
2345 pass
2346 for key in section_dict.keys():
2347 if type(section_dict[key]) == BooleanType:
2348 self.set_bool_opt(section_name + "." + key, section_dict[key])
2349 elif type(section_dict[key]) == IntType:
2350 self.set_int_opt(section_name + "." + key, section_dict[key])
2351 elif type(section_dict[key]) == FloatType:
2352 self.set_float_opt(section_name + "." + key, section_dict[key])
2353 else:
2354 self.set_opt(section_name + "." + key, section_dict[key])
2356 # Return the profile recorded in the specified section.
2358 #Parameters:
2360 # 'section_name' -- string - Configuration file section.
2362 #Returns:
2364 # dictionary or None - The specified profile or None if not found
2365 def get_profile( self, section_name ):
2366 if section_name in self.profiles():
2367 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' ]
2368 bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
2369 int_types = [ 'signal' ]
2370 profile = get_new_profile()
2371 for option in bool_types:
2372 option_tmp = self.get_opt_as_bool(section_name + "." + option)
2373 if option_tmp:
2374 profile[option] = option_tmp
2375 for option in int_types:
2376 option_tmp = self.get_opt_as_int(section_name + "." + option)
2377 if option_tmp:
2378 profile[option] = option_tmp
2379 for option in str_types:
2380 option_tmp = self.get_opt(section_name + "." + option)
2381 if option_tmp:
2382 profile[option] = option_tmp
2383 return profile
2384 return None
2386 # Get a config option and handle exceptions.
2388 #Parameters:
2390 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2391 # period and the option key. (E.g. "DEFAULT.interface")
2393 #Returns:
2395 # string or None - option value as string or None on failure
2396 def get_opt( self, option_path ):
2397 #print "ConfigFile.get_opt: ", option_path
2398 (section, option) = option_path.split('.')
2399 try:
2400 return self.get(section, option, self.raw)
2401 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2402 return None
2404 # Get a config option and return as a boolean type.
2406 #Parameters:
2408 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2409 # period and the option key. (E.g. "DEFAULT.interface")
2411 #Returns:
2413 # boolean - option value as boolean
2414 def get_opt_as_bool( self, option_path ):
2415 option = self.get_opt(option_path)
2416 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2417 return option
2418 if option == 'True':
2419 return True
2420 if option == 'False':
2421 return False
2422 raise ValueError, 'boolean option was not True or False'
2424 # Get a config option and return as an integer type.
2426 #Parameters:
2428 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2429 # period and the option key. (E.g. "DEFAULT.interface")
2431 #Returns:
2433 # integer- option value as integer
2434 def get_opt_as_int( self, option_path ):
2435 return int(float(self.get_opt(option_path)))
2437 # Convert boolean type to string and set config option.
2439 #Parameters:
2441 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2442 # period and the option key. (E.g. "DEFAULT.interface")
2444 # 'value' -- boolean - Value to set.
2446 #Returns:
2448 # nothing
2449 def set_bool_opt( self, option_path, value ):
2450 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2451 value == 'True'
2452 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2453 value == 'False'
2454 else:
2455 raise ValueError, 'cannot convert value to string'
2456 self.set_opt(option_path, repr(value))
2458 # Convert integer type to string and set config option.
2460 #Parameters:
2462 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2463 # period and the option key. (E.g. "DEFAULT.interface")
2465 # 'value' -- integer - Value to set.
2467 #Returns:
2469 # nothing
2470 def set_int_opt( self, option_path, value ):
2471 if not isinstance(value, IntType):
2472 raise ValueError, 'value is not an integer'
2473 self.set_opt(option_path, repr(value))
2475 # Convert float type to string and set config option.
2477 #Parameters:
2479 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2480 # period and the option key. (E.g. "DEFAULT.interface")
2482 # 'value' -- float - Value to set.
2484 #Returns:
2486 # nothing
2487 def set_float_opt( self, option_path, value ):
2488 if not isinstance(value, FloatType):
2489 raise ValueError, 'value is not a float'
2490 self.set_opt(option_path, repr(int(value)))
2492 # Set a config option while handling exceptions.
2494 #Parameters:
2496 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2497 # period and the option key. (E.g. "DEFAULT.interface")
2499 # 'value' -- string - Value to set.
2501 #Returns:
2503 # nothing
2504 def set_opt( self, option_path, value ):
2505 (section, option) = option_path.split('.')
2506 try:
2507 self.set(section, option, value)
2508 except ConfigParser.NoSectionError:
2509 self.add_section(section)
2510 self.set_opt(option_path, value)
2512 # Return a list of the section names which denote AP profiles.
2514 #Parameters:
2516 # nothing
2518 #Returns:
2520 # list - profile names
2521 def profiles( self ):
2522 profile_list = []
2523 for section in self.sections():
2524 if ':' in section:
2525 profile_list.append(section)
2526 return profile_list
2528 # Read configuration file from disk into instance variables.
2530 #Parameters:
2532 # nothing
2534 #Returns:
2536 # nothing
2537 def read( self ):
2538 fp = open( self.filename, "r" )
2539 self.readfp(fp)
2540 # convert the auto_profile_order to a list for ordering
2541 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2542 for ap in self.profiles():
2543 self.set_bool_opt( ap + '.known', True)
2544 if ap in self.auto_profile_order: continue
2545 self.auto_profile_order.append( ap )
2546 fp.close()
2548 # Write configuration file to disk from instance variables. Copied from
2549 # ConfigParser and modified to write options in alphabetical order.
2551 #Parameters:
2553 # nothing
2555 #Returns:
2557 # nothing
2558 def write( self ):
2559 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2560 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2561 fp = open( self.filename, "w" )
2562 # write DEFAULT section first
2563 if self._defaults:
2564 fp.write("[DEFAULT]\n")
2565 for key in sorted(self._defaults.keys()):
2566 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2567 fp.write("\n")
2568 # write non-profile sections first
2569 for section in self._sections:
2570 if section not in self.profiles():
2571 fp.write("[%s]\n" % section)
2572 for key in sorted(self._sections[section].keys()):
2573 if key != "__name__":
2574 fp.write("%s = %s\n" %
2575 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2576 fp.write("\n")
2577 # write profile sections
2578 for section in self._sections:
2579 if section in self.profiles():
2580 fp.write("[%s]\n" % section)
2581 for key in sorted(self._sections[section].keys()):
2582 if key != "__name__":
2583 fp.write("%s = %s\n" %
2584 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2585 fp.write("\n")
2586 fp.close()
2590 # Load our conf file and known profiles
2591 # Defaults, these may get overridden by values found in the conf file.
2592 config_defaults = { # The network interface you use.
2593 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2594 'interface': "auto_detect",
2595 # How long should the scan for access points last?
2596 #'scan_timeout': '5',
2597 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2598 # Set the speak_up option to false if you do not have or want this.
2599 'speak_command': '/usr/bin/say',
2600 # Should I speak up when connecting to a network? (If you have a speech command)
2601 'speak_up': 'False',
2602 # You may set this to true for cards that require a "commit" command with iwconfig
2603 'commit_required': 'False',
2604 # You may set this to true for cards that require the interface to be brought up first
2605 'ifup_required': 'False',
2606 # set the location of the log file
2607 'logfile': './wifi-radar.log',
2608 # Set the location of several important programs
2609 'iwlist_command': '/sbin/iwlist',
2610 'iwconfig_command': '/sbin/iwconfig',
2611 'ifconfig_command': '/sbin/ifconfig',
2612 'route_command': '/sbin/route',
2613 'auto_profile_order': '[]',
2614 'version': WIFI_RADAR_VERSION }
2616 config_dhcp = { # DHCP client
2617 'command': 'dhcpcd',
2618 # How long to wait for an IP addr from DHCP server
2619 'timeout': '30',
2620 # Arguments to use with DHCP client on connect
2621 'args': '-D -o -i dhcp_client -t %(timeout)s',
2622 # Argument to use with DHCP client on disconnect
2623 'kill_args': '-k',
2624 # The file where DHCP client PID is written
2625 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2627 config_wpa = { # WPA Supplicant
2628 'command': '/usr/sbin/wpa_supplicant',
2629 # Arguments to use with WPA Supplicant on connect
2630 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2631 # Arguments to use with WPA Supplicant on disconnect
2632 'kill_command': '',
2633 # Where the WPA Supplicant config file can be found
2634 'configuration': '/etc/wpa_supplicant.conf',
2635 # Driver to use with WPA Supplicant
2636 'driver': 'wext',
2637 # The file where WPA Supplicant PID is written
2638 'pidfile': '/var/run/wpa_supplicant.pid' }
2640 # initialize config, with defaults
2641 confFile = ConfigFile(CONF_FILE, config_defaults)
2642 confFile.set_section("DHCP", config_dhcp)
2643 confFile.set_section("WPA", config_wpa)
2645 if not os.path.isfile( CONF_FILE ):
2646 confFile.set_bool_opt('DEFAULT.new_file', True)
2647 else:
2648 if not os.access(CONF_FILE, os.R_OK):
2649 print "Can't open " + CONF_FILE + "."
2650 print "Are you root?"
2651 sys.exit()
2652 confFile.read()
2655 ####################################################################################################
2656 # Embedded Images
2657 wifi_radar_icon = [ ""
2658 "GdkP"
2659 "\0\0\22""7"
2660 "\2\1\0\2"
2661 "\0\0\1\214"
2662 "\0\0\0c"
2663 "\0\0\0O"
2664 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2665 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2666 "\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"
2667 "\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"
2668 "\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"
2669 "\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"
2670 "\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"
2671 "\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"
2672 "\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"
2673 "\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"
2674 "\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"
2675 "\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"
2676 "\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"
2677 "\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"
2678 "\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"
2679 "\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"
2680 "\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"
2681 "\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"
2682 "\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"
2683 "\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"
2684 "\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"
2685 "\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"
2686 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2687 "\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"
2688 "\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"
2689 "\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"
2690 "\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"
2691 "\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"
2692 "\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"
2693 "\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"
2694 "\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"
2695 "\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"
2696 "\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"
2697 "\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"
2698 "\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"
2699 "\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"
2700 "\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"
2701 "\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"
2702 "\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"
2703 "\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"
2704 "\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"
2705 "\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"
2706 "\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"
2707 "\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"
2708 "\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"
2709 "\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"
2710 "\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"
2711 "\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"
2712 "\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"
2713 "\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"
2714 "\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"
2715 "\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"
2716 "\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"
2717 "\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"
2718 "\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"
2719 "\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"
2720 "\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"
2721 "\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"
2722 "\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"
2723 "\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"
2724 "\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"
2725 "\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"
2726 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2727 "\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"
2728 "\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"
2729 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2730 "\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"
2731 "\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"
2732 "\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"
2733 "\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"
2734 "\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"
2735 "\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"
2736 "\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"
2737 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2738 "\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"
2739 "\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"
2740 "\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"
2741 "\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"
2742 "\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"
2743 "\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"
2744 "|\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"
2745 "\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"
2746 "\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"
2747 "\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"
2748 "\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"
2749 "\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"
2750 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2751 "\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"
2752 "\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"
2753 "\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"
2754 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2755 "\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"
2756 "\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"
2757 "\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"
2758 "\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"
2759 "\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"
2760 "\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"
2761 "\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"
2762 "\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"
2763 "\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"
2764 "\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"
2765 "\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"
2766 "\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"
2767 "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"
2768 "\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"
2769 "\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"
2770 "\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"
2771 "\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"
2772 "\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"
2773 "\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"
2774 "\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"
2775 "\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"
2776 "\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"
2777 "\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"
2778 "\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"
2779 "\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"
2780 "\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"
2781 "\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"
2782 "\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|"
2783 "\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"
2784 "\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"
2785 "\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"
2786 "\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"
2787 "\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"
2788 "\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"
2789 "\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"
2790 "\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"
2791 "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"
2792 "\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"
2793 "\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"
2794 "\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"
2795 "\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"
2796 "\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"
2797 "\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"
2798 "\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"
2799 "\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"
2800 "\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"
2801 "\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"
2802 "\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"
2803 "\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"
2804 "\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"
2805 "\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"
2806 "\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"
2807 "\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"
2808 "\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"
2809 "\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"
2810 "\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"
2811 "\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"
2812 "\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"
2813 "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"
2814 "\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"
2815 "\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"
2816 "\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"
2817 "\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"
2818 "\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"
2819 "\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"
2820 "\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"
2821 "\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"
2822 "\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"
2823 "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"
2824 "\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"
2825 "\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"
2826 "\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"
2827 "\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"
2828 "\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"
2829 "\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"
2830 "\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"
2831 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2832 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2833 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2834 "\0"]
2836 known_profile_icon = [ ""
2837 "GdkP"
2838 "\0\0\5""0"
2839 "\2\1\0\2"
2840 "\0\0\0P"
2841 "\0\0\0\24"
2842 "\0\0\0\24"
2843 "\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"
2844 "\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"
2845 "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"
2846 "\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"
2847 "\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"
2848 "\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"
2849 "\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"
2850 "\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"
2851 "\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"
2852 "\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"
2853 "\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"
2854 "\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"
2855 "\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"
2856 "\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"
2857 "\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"
2858 "\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"
2859 "\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"
2860 "\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"
2861 "\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"
2862 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2863 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2864 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2865 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2866 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2867 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2868 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2869 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2870 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2871 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2872 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2873 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2874 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2875 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2876 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2877 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2878 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2879 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2880 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2881 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2882 "\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"
2883 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2884 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2885 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2886 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2887 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2888 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2889 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2890 "\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"
2891 "\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"
2892 "\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"
2893 "\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"
2894 "\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"]
2896 unknown_profile_icon = [ ""
2897 "GdkP"
2898 "\0\0\5\22"
2899 "\2\1\0\2"
2900 "\0\0\0P"
2901 "\0\0\0\24"
2902 "\0\0\0\24"
2903 "\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"
2904 "\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"
2905 "\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"
2906 "\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"
2907 "(\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"
2908 "\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"
2909 "#\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"
2910 "\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"
2911 "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"
2912 "\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"
2913 "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"
2914 "\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"
2915 "\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"
2916 "\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"
2917 "\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"
2918 "\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"
2919 "\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"
2920 "\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"
2921 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2922 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2923 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2924 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2925 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2926 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2927 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2928 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2929 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2930 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2931 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2932 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2933 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2934 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2935 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2936 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2937 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2938 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2939 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2940 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2941 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2942 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2943 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2944 "\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"
2945 "\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"
2946 "\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"
2947 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2949 signal_xpm_barely = [
2950 "20 20 10 1",
2951 " c None",
2952 ". c #C6C6C6",
2953 "+ c #CCCCCC",
2954 "@ c #DBDBDB",
2955 "# c #D3D3D3",
2956 "$ c #A9B099",
2957 "% c #95A173",
2958 "& c #6B8428",
2959 "* c #B4B7AC",
2960 "= c #80924D",
2961 " .+++.",
2962 " +@@@+",
2963 " +@@@+",
2964 " +@@@+",
2965 " +@@@+",
2966 " .++++#@@@+",
2967 " +@@@@@@@@+",
2968 " +@@@@@@@@+",
2969 " +@@@@@@@@+",
2970 " +@@@@@@@@+",
2971 " $%%%%#@@@@@@@@+",
2972 " %&&&&@@@@@@@@@+",
2973 " %&&&&@@@@@@@@@+",
2974 " %&&&&@@@@@@@@@+",
2975 " %&&&&@@@@@@@@@+",
2976 "*%%%%=&&&&@@@@@@@@@+",
2977 "%&&&&&&&&&@@@@@@@@@+",
2978 "%&&&&&&&&&@@@@@@@@@+",
2979 "%&&&&&&&&&@@@@@@@@@+",
2980 "*%%%%%%%%%+++++++++."
2984 signal_xpm_best = [
2985 "20 20 6 1",
2986 " c None",
2987 ". c #9DAABF",
2988 "+ c #7B96BF",
2989 "@ c #386EBF",
2990 "# c #5982BF",
2991 "$ c #AEB4BF",
2992 " .+++.",
2993 " +@@@+",
2994 " +@@@+",
2995 " +@@@+",
2996 " +@@@+",
2997 " .++++#@@@+",
2998 " +@@@@@@@@+",
2999 " +@@@@@@@@+",
3000 " +@@@@@@@@+",
3001 " +@@@@@@@@+",
3002 " .++++#@@@@@@@@+",
3003 " +@@@@@@@@@@@@@+",
3004 " +@@@@@@@@@@@@@+",
3005 " +@@@@@@@@@@@@@+",
3006 " +@@@@@@@@@@@@@+",
3007 "$++++#@@@@@@@@@@@@@+",
3008 "+@@@@@@@@@@@@@@@@@@+",
3009 "+@@@@@@@@@@@@@@@@@@+",
3010 "+@@@@@@@@@@@@@@@@@@+",
3011 "$++++++++++++++++++."
3014 signal_xpm_none = [
3015 "20 20 6 1",
3016 " c None",
3017 ". c #C6C6C6",
3018 "+ c #CCCCCC",
3019 "@ c #DBDBDB",
3020 "# c #D3D3D3",
3021 "$ c #C2C2C2",
3022 " .+++.",
3023 " +@@@+",
3024 " +@@@+",
3025 " +@@@+",
3026 " +@@@+",
3027 " .++++#@@@+",
3028 " +@@@@@@@@+",
3029 " +@@@@@@@@+",
3030 " +@@@@@@@@+",
3031 " +@@@@@@@@+",
3032 " .++++#@@@@@@@@+",
3033 " +@@@@@@@@@@@@@+",
3034 " +@@@@@@@@@@@@@+",
3035 " +@@@@@@@@@@@@@+",
3036 " +@@@@@@@@@@@@@+",
3037 "$++++#@@@@@@@@@@@@@+",
3038 "+@@@@@@@@@@@@@@@@@@+",
3039 "+@@@@@@@@@@@@@@@@@@+",
3040 "+@@@@@@@@@@@@@@@@@@+",
3041 "$++++++++++++++++++."
3044 signal_xpm_ok = [
3045 "20 20 10 1",
3046 " c None",
3047 ". c #C6C6C6",
3048 "+ c #CCCCCC",
3049 "@ c #DBDBDB",
3050 "# c #A1A5B2",
3051 "$ c #848DA5",
3052 "% c #D3D3D3",
3053 "& c #4A5B8C",
3054 "* c #677498",
3055 "= c #B0B2B8",
3056 " .+++.",
3057 " +@@@+",
3058 " +@@@+",
3059 " +@@@+",
3060 " +@@@+",
3061 " #$$$$%@@@+",
3062 " $&&&&@@@@+",
3063 " $&&&&@@@@+",
3064 " $&&&&@@@@+",
3065 " $&&&&@@@@+",
3066 " #$$$$*&&&&@@@@+",
3067 " $&&&&&&&&&@@@@+",
3068 " $&&&&&&&&&@@@@+",
3069 " $&&&&&&&&&@@@@+",
3070 " $&&&&&&&&&@@@@+",
3071 "=$$$$*&&&&&&&&&@@@@+",
3072 "$&&&&&&&&&&&&&&@@@@+",
3073 "$&&&&&&&&&&&&&&@@@@+",
3074 "$&&&&&&&&&&&&&&@@@@+",
3075 "=$$$$$$$$$$$$$$++++."
3079 signal_xpm_low = [
3080 "20 20 8 1",
3081 " c None",
3082 ". c #C6C6C6",
3083 "+ c #CCCCCC",
3084 "@ c #DBDBDB",
3085 "# c #D3D3D3",
3086 "$ c #BFB0B5",
3087 "% c #C18799",
3088 "& c #C54F74",
3089 " .+++.",
3090 " +@@@+",
3091 " +@@@+",
3092 " +@@@+",
3093 " +@@@+",
3094 " .++++#@@@+",
3095 " +@@@@@@@@+",
3096 " +@@@@@@@@+",
3097 " +@@@@@@@@+",
3098 " +@@@@@@@@+",
3099 " .++++#@@@@@@@@+",
3100 " +@@@@@@@@@@@@@+",
3101 " +@@@@@@@@@@@@@+",
3102 " +@@@@@@@@@@@@@+",
3103 " +@@@@@@@@@@@@@+",
3104 "$%%%%#@@@@@@@@@@@@@+",
3105 "%&&&&@@@@@@@@@@@@@@+",
3106 "%&&&&@@@@@@@@@@@@@@+",
3107 "%&&&&@@@@@@@@@@@@@@+",
3108 "$%%%%++++++++++++++."
3112 ####################################################################################################
3113 # Make so we can be imported
3114 if __name__ == "__main__":
3115 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
3116 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
3117 else:
3118 import gtk, gobject
3119 gtk.gdk.threads_init()
3120 apQueue = Queue.Queue(100)
3121 commQueue = Queue.Queue(2)
3123 logger = logging.getLogger("wrlog")
3124 logger.setLevel(logging.WARNING)
3125 fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
3126 fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
3127 logger.addHandler(fileLogHandler)
3128 if __debug__:
3129 logger.setLevel(logging.DEBUG)
3130 consoleLogHandler = logging.StreamHandler()
3131 consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
3132 logger.addHandler(consoleLogHandler)
3134 exit_event = threading.Event()
3135 exit_event.clear()
3136 threading.Thread(None, scanning_thread, None, (confFile, apQueue, commQueue, logger, exit_event)).start()
3137 main_radar_window = radar_window(confFile, apQueue, commQueue, logger, exit_event)
3138 gobject.timeout_add( 500, main_radar_window.update_plist_items )
3139 main_radar_window.main()