Set encryption icon based on scanned info
[wifi-radar.git] / wifi-radar
blob8707f871d6adfb9ab41d9225e0f342bd5dc053ec
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['security'])
360 iwconfig_command.append( "'" + self.profile['key'] + "'" )
361 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
362 # Setting mode
363 if self.profile['mode'].lower() == 'master' or self.profile['mode'].lower() == 'auto':
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 self.commQueue.join()
515 except Queue.Full:
516 pass
517 if self.state:
518 self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), self.get_current_bssid()))
519 if not self.profile:
520 self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), ''))
521 if not self.profile:
522 raise KeyError
523 # Let's run the disconnection prescript
524 if self.profile['dis_prescript'].strip() != '':
525 self.logger.info("executing disconnection prescript: %s" % (self.profile['dis_prescript'], ))
526 shellcmd([self.profile['dis_prescript']],
527 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
528 "WIFIRADAR_ESSID": self.get_current_essid() or '',
529 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
530 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
533 self.logger.info("Kill off any existing DHCP clients running...")
534 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
535 self.logger.info("Killing existing DHCP...")
536 try:
537 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
538 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
539 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
540 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
541 self.logger.info("DHCP command: %s" % (dhcp_command, ))
542 # call DHCP client command and wait for return
543 if not shellcmd(dhcp_command): return
544 else:
545 self.logger.info("Killing DHCP manually...")
546 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
547 except OSError:
548 print "failed to kill DHCP client"
549 self.logger.info("Kill off any existing WPA supplicants running...")
550 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
551 self.logger.info("Killing existing WPA supplicant...")
552 try:
553 if not self.confFile.get_opt('WPA.kill_command') != '':
554 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
555 if not shellcmd(wpa_command): return
556 else:
557 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
558 except OSError:
559 print "failed to kill WPA supplicant"
560 self.logger.info("Let's clear out the wireless stuff")
561 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
562 self.logger.info("Now take the interface down")
563 self.logger.info("Since it may be brought back up by the next scan, lets unset its IP")
564 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
565 # taking down the interface too quickly can crash my system, so pause a moment
566 sleep(1)
567 self.if_change('down')
568 # Let's run the disconnection postscript
569 if self.profile['dis_postscript'].strip() != '':
570 self.logger.info("executing disconnection postscript: %s" % (self.profile['dis_postscript'], ))
571 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
572 self.state = False
573 self.logger.info("Disconnect complete.")
574 # Begin scanning again
575 try:
576 self.commQueue.put("scan")
577 except Queue.Full:
578 pass
580 # Returns the current IP, if any, by calling ifconfig.
582 #Parameters:
584 # nothing
586 #Returns:
588 # string or None -- the IP address or None (if no there is no current connection)
589 def get_current_ip( self ):
590 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
591 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
592 # Be careful to the language (inet adr: in French for example)
594 # Hi Brian
596 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
597 # There the string in ifconfig is inet Adresse for the IP which isn't
598 # found by the current get_current_ip function in wifi-radar. I changed
599 # the according line (#289; gentoo, v1.9.6-r1) to
600 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
601 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
603 # I'd be happy if you could incorporate this small change because as now
604 # I've got to change the file every time it is updated.
606 # Best wishes
608 # Simon
609 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
610 line = ifconfig_info.read()
611 if ip_re.search( line ):
612 return ip_re.search( line ).group(1)
613 return None
615 # Returns the current ESSID, if any, by calling iwconfig.
617 #Parameters:
619 # nothing
621 #Returns:
623 # string or None -- the ESSID or None (if no there is no current association)
624 def get_current_essid( self ):
625 """Returns the current ESSID if any by calling iwconfig"""
626 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
627 # Be careful to the language (inet adr: in French for example)
628 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
629 line = iwconfig_info.read()
630 if essid_re.search( line ):
631 return essid_re.search( line ).group(2)
632 return None
634 # Returns the current BSSID, if any, by calling iwconfig.
636 #Parameters:
638 # nothing
640 #Returns:
642 # string or None -- the BSSID or None (if no there is no current association)
643 def get_current_bssid( self ):
644 """Returns the current BSSID if any by calling iwconfig"""
645 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
646 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
647 line = iwconfig_info.read()
648 if bssid_re.search( line ):
649 return bssid_re.search( line ).group(2)
650 return None
654 # The main user interface window for WiFi Radar. This class also is the control
655 # center for most of the rest of the operations.
656 class radar_window:
657 # Create a new radar_window.
659 #Parameters:
661 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
663 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
665 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
667 # 'logger' -- Logger - Python's logging facility
669 #Returns:
671 # radar_window instance
672 def __init__(self, confFile, apQueue, commQueue, logger, exit_event):
673 global signal_xpm_none
674 global signal_xpm_low
675 global signal_xpm_barely
676 global signal_xpm_ok
677 global signal_xpm_best
678 global known_profile_icon
679 global unknown_profile_icon
680 global wifi_radar_icon
682 self.confFile = confFile
683 self.apQueue = apQueue
684 self.commandQueue = commQueue
685 self.logger = logger
686 self.access_points = {}
687 self.exit_event = exit_event
688 self.connection = None
690 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
691 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
692 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
693 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
694 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
695 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
696 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
697 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
698 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
699 self.window.set_icon( icon )
700 self.window.set_border_width( 10 )
701 self.window.set_size_request( 550, 300 )
702 self.window.set_title( "WiFi Radar" )
703 self.window.connect( 'delete_event', self.delete_event )
704 self.window.connect( 'destroy', self.destroy )
705 # let's create all our widgets
706 self.current_network = gtk.Label()
707 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
708 self.current_network.show()
709 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
710 self.close_button.show()
711 self.close_button.connect( 'clicked', self.delete_event, None )
712 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
713 self.about_button.show()
714 self.about_button.connect( 'clicked', self.show_about_info, None )
715 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
716 self.preferences_button.show()
717 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
718 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
719 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
720 self.plist = gtk.TreeView( self.pstore )
721 # The icons column, known and encryption
722 self.pix_cell = gtk.CellRendererPixbuf()
723 self.wep_cell = gtk.CellRendererPixbuf()
724 self.icons_cell = gtk.CellRendererText()
725 self.icons_col = gtk.TreeViewColumn()
726 self.icons_col.pack_start( self.pix_cell, False )
727 self.icons_col.pack_start( self.wep_cell, False )
728 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
729 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
730 self.plist.append_column( self.icons_col )
731 # The AP column
732 self.ap_cell = gtk.CellRendererText()
733 self.ap_col = gtk.TreeViewColumn( "Access Point" )
734 self.ap_col.pack_start( self.ap_cell, True )
735 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
736 self.plist.append_column( self.ap_col )
737 # The signal column
738 self.sig_cell = gtk.CellRendererPixbuf()
739 self.signal_col = gtk.TreeViewColumn( "Signal" )
740 self.signal_col.pack_start( self.sig_cell, True )
741 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
742 self.plist.append_column( self.signal_col )
743 # The mode column
744 self.mode_cell = gtk.CellRendererText()
745 self.mode_col = gtk.TreeViewColumn( "Mode" )
746 self.mode_col.pack_start( self.mode_cell, True )
747 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
748 self.plist.append_column( self.mode_col )
749 # The protocol column
750 self.prot_cell = gtk.CellRendererText()
751 self.protocol_col = gtk.TreeViewColumn( "802.11" )
752 self.protocol_col.pack_start( self.prot_cell, True )
753 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
754 self.plist.append_column( self.protocol_col )
755 # The channel column
756 self.channel_cell = gtk.CellRendererText()
757 self.channel_col = gtk.TreeViewColumn( "Channel" )
758 self.channel_col.pack_start( self.channel_cell, True )
759 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
760 self.plist.append_column( self.channel_col )
761 # DnD Ordering
762 self.plist.set_reorderable( True )
763 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
764 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
765 # enable/disable buttons based on the selected network
766 self.selected_network = self.plist.get_selection()
767 self.selected_network.connect( 'changed', self.on_network_selection, None )
768 # the list scroll bar
769 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
770 sb.show()
771 self.plist.show()
772 # Add New button
773 self.new_button = gtk.Button( "_New" )
774 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
775 self.new_button.show()
776 # Add Configure button
777 self.edit_button = gtk.Button( "C_onfigure" )
778 self.edit_button.connect( 'clicked', self.edit_profile, None )
779 self.edit_button.show()
780 self.edit_button.set_sensitive(False)
781 # Add Delete button
782 self.delete_button = gtk.Button( "_Delete" )
783 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
784 self.delete_button.show()
785 self.delete_button.set_sensitive(False)
786 # Add Connect button
787 self.connect_button = gtk.Button( "Co_nnect" )
788 self.connect_button.connect( 'clicked', self.connect_profile, None )
789 # Add Disconnect button
790 self.disconnect_button = gtk.Button( "D_isconnect" )
791 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
792 # lets add our widgets
793 rows = gtk.VBox( False, 3 )
794 net_list = gtk.HBox( False, 0 )
795 listcols = gtk.HBox( False, 0 )
796 prows = gtk.VBox( False, 0 )
797 # lets start packing
798 # the network list
799 net_list.pack_start( self.plist, True, True, 0 )
800 net_list.pack_start( sb, False, False, 0 )
801 # the rows level
802 rows.pack_start( net_list , True, True, 0 )
803 rows.pack_start( self.current_network, False, True, 0 )
804 # the list columns
805 listcols.pack_start( rows, True, True, 0 )
806 listcols.pack_start( prows, False, False, 5 )
807 # the list buttons
808 prows.pack_start( self.new_button, False, False, 2 )
809 prows.pack_start( self.edit_button, False, False, 2 )
810 prows.pack_start( self.delete_button, False, False, 2 )
811 prows.pack_end( self.connect_button, False, False, 2 )
812 prows.pack_end( self.disconnect_button, False, False, 2 )
814 self.window.action_area.pack_start( self.about_button )
815 self.window.action_area.pack_start( self.preferences_button )
816 self.window.action_area.pack_start( self.close_button )
818 rows.show()
819 prows.show()
820 listcols.show()
821 self.window.vbox.add( listcols )
822 self.window.vbox.set_spacing( 3 )
823 self.window.show_all()
825 # Now, immediately hide these two. The proper one will be
826 # displayed later, based on interface state. -BEF-
827 self.disconnect_button.hide()
828 self.connect_button.hide()
829 self.connect_button.set_sensitive(False)
831 # set up connection manager for later use
832 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
833 # set up status window for later use
834 self.status_window = StatusWindow( self )
835 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
837 # Add our known profiles in order
838 for ap in self.confFile.auto_profile_order:
839 ap = ap.strip()
840 self.access_points[ ap ] = self.confFile.get_profile( ap )
841 wep = None
842 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
843 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'] ] )
844 # This is the first run (or, at least, no config file was present), so pop up the preferences window
845 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
846 self.confFile.remove_option('DEFAULT', 'new_file')
847 self.edit_preferences(self.preferences_button)
849 # Begin running radar_window in Gtk event loop.
851 #Parameters:
853 # nothing
855 #Returns:
857 # nothing
858 def main( self ):
859 gtk.main()
861 # Quit application.
863 #Parameters:
865 # 'widget' -- gtk.Widget - The widget sending the event.
867 #Returns:
869 # nothing
870 def destroy( self, widget = None):
871 if self.status_window:
872 self.status_window.destroy()
873 gtk.main_quit()
875 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
877 #Parameters:
879 # 'widget' -- gtk.Widget - The widget sending the event.
881 # 'data' -- tuple - list of arbitrary arguments (not used)
883 #Returns:
885 # boolean -- always return False (i.e. do not propigate the signal which called)
886 def delete_event( self, widget, data = None ):
887 # Let other threads know it is time to exit
888 self.exit_event.set()
889 # Wait for all other threads to exit before continuing
890 while threading.activeCount() > 1:
891 sleep(0.25)
892 self.destroy()
893 return False
895 # Updates the on-screen profiles list.
897 #Parameters:
899 # nothing
901 #Returns:
903 # boolean -- always return True
904 def update_plist_items( self ):
905 # Indicate to PyGtk that only one Gtk thread should run here
906 gtk.gdk.threads_enter()
907 # update the current ip and essid
908 # set the state of connect/disconnect buttons based on whether we have an IP address
909 if self.connection:
910 if self.connection.state:
911 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() ) )
912 self.connect_button.hide()
913 self.disconnect_button.show()
914 else:
915 self.current_network.set_text( "Not Connected." )
916 self.disconnect_button.hide()
917 self.connect_button.show()
919 while True:
920 # Get profiles scanned by iwlist
921 try:
922 ap = self.apQueue.get_nowait()
923 except Queue.Empty:
924 break
925 else:
926 profile = self.confFile.get_profile(make_section_name(ap['essid'], ap['bssid']))
927 if not profile:
928 profile = self.confFile.get_profile(make_section_name(ap['essid'], ''))
929 if not profile:
930 profile = get_new_profile()
931 if profile['roaming']:
932 prow_iter = self.get_row_by_ap(ap['essid'], ' Multiple APs')
933 else:
934 prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
935 wep = None
936 if prow_iter != None:
937 # the AP is in the list of APs on the screen
938 apname = make_section_name(ap['essid'], ap['bssid'])
939 if self.access_points.has_key(apname):
940 # This AP has been configured and is/should be stored in the config file
941 ap['known'] = self.access_points[apname]['known']
942 self.access_points[apname]['available'] = ap['available']
943 self.access_points[apname]['encrypted'] = ap['encrypted']
944 self.access_points[apname]['signal'] = ap['signal']
945 self.access_points[apname]['mode'] = ap['mode']
946 self.access_points[apname]['protocol'] = ap['protocol']
947 self.access_points[apname]['channel'] = ap['channel']
948 # Set the 'known' values; False is default, overridden to True by self.access_points
949 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
950 self.pstore.set_value(prow_iter, 2, ap['known'])
951 self.pstore.set_value(prow_iter, 3, ap['available'])
952 if ap['encrypted']:
953 wep = gtk.STOCK_DIALOG_AUTHENTICATION
954 self.pstore.set_value(prow_iter, 4, wep)
955 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(ap['signal']))
956 self.pstore.set_value(prow_iter, 6, ap['mode'])
957 self.pstore.set_value(prow_iter, 7, ap['protocol'])
958 self.pstore.set_value(prow_iter, 8, ap['channel'])
959 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
960 #for val in self.pstore[prow_iter]:
961 #print val,
962 else:
963 # the AP is not in the list of APs on the screen
964 if profile['roaming']:
965 ap_name = ap['essid'] + "\n" + ' Multiple APs'
966 else:
967 ap_name = ap['essid'] + "\n" + ap['bssid']
968 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']])
969 #print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']
970 # Allow other Gtk threads to run
971 gtk.gdk.threads_leave()
972 #print "update_plist_items: Empty apQueue"
973 return True
975 # Return the proper icon for a value of known.
977 #Parameters:
979 # 'known' -- boolean - Whether the AP is known (i.e. configured)
981 #Returns:
983 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
984 def pixbuf_from_known( self, known ):
985 """ return the proper icon for value of known """
986 if known:
987 return self.known_profile_icon
988 else:
989 return self.unknown_profile_icon
991 # Return an icon indicating the signal level.
993 #Parameters:
995 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
997 #Returns:
999 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
1000 def pixbuf_from_signal( self, signal ):
1001 signal = int( signal )
1002 # shift signal up by 80 to convert dBm scale to arbitrary scale
1003 if signal < 0: signal = signal + 80
1004 #print "signal level:", signal
1005 if signal < 3:
1006 return self.signal_none_pb
1007 elif signal < 12:
1008 return self.signal_low_pb
1009 elif signal < 20:
1010 return self.signal_barely_pb
1011 elif signal < 35:
1012 return self.signal_ok_pb
1013 elif signal >= 35:
1014 return self.signal_best_pb
1015 else:
1016 return None
1018 # Return row which holds specified ESSID and BSSID.
1020 #Parameters:
1022 # 'essid' -- string - ESSID to match
1024 # 'bssid' -- string - BSSID to match
1026 #Returns:
1028 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1029 def get_row_by_ap( self, essid, bssid ):
1030 for row in self.pstore:
1031 if ( row[0] == essid + "\n" + bssid ):
1032 #print "matched:", row.iter, essid, bssid
1033 return row.iter
1034 return None
1036 # Enable/disable buttons based on the selected network.
1038 #Parameters:
1040 # 'widget' -- gtk.Widget - The widget sending the event.
1042 # 'data' -- tuple - list of arbitrary arguments (not used)
1044 #Returns:
1046 # nothing
1047 def on_network_selection( self, widget, data = None ):
1048 ( store, selected_iter ) = self.selected_network.get_selected()
1049 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1050 # if no networks are selected, disable all buttons except New
1051 # (this occurs after a drag-and-drop)
1052 if selected_iter == None:
1053 self.edit_button.set_sensitive(False)
1054 self.delete_button.set_sensitive(False)
1055 self.connect_button.set_sensitive(False)
1056 return
1057 # enable/disable buttons
1058 self.connect_button.set_sensitive(True)
1059 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1060 self.edit_button.set_sensitive(True)
1061 self.delete_button.set_sensitive(True)
1062 else:
1063 self.edit_button.set_sensitive(True)
1064 self.delete_button.set_sensitive(False)
1066 # Init and run the about dialog
1068 #Parameters:
1070 # 'widget' -- gtk.Widget - The widget sending the event.
1072 # 'data' -- tuple - list of arbitrary arguments (not used)
1074 #Returns:
1076 # nothing
1077 def show_about_info( self, widget, data=None ):
1078 about = about_dialog()
1079 about.run()
1080 about.destroy()
1082 # Init and run the preferences dialog
1084 #Parameters:
1086 # 'widget' -- gtk.Widget - The widget sending the event.
1088 # 'data' -- tuple - list of arbitrary arguments (not used)
1090 #Returns:
1092 # nothing
1093 def edit_preferences( self, widget, data=None ):
1094 # get raw strings from config file
1095 self.confFile.raw = True
1096 prefs = preferences_dialog( self, self.confFile )
1097 response = prefs.run()
1098 prefs.destroy()
1099 if response == int(gtk.RESPONSE_ACCEPT):
1100 prefs.save()
1101 # get cooked strings from config file
1102 self.confFile.raw = False
1104 # Respond to a request to create a new AP profile
1106 #Parameters:
1108 # 'widget' -- gtk.Widget - The widget sending the event.
1110 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1112 # 'data' -- tuple - list of arbitrary arguments (not used)
1114 #Returns:
1116 # boolean -- True if a profile was created and False if profile creation was canceled.
1117 def create_new_profile( self, widget, profile, data=None ):
1118 profile_editor = profile_dialog( self, profile )
1119 try:
1120 profile = profile_editor.run()
1121 except ValueError:
1122 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1123 del error_dlg
1124 return False
1125 finally:
1126 profile_editor.destroy()
1127 if profile:
1128 apname = make_section_name( profile['essid'], profile['bssid'] )
1129 # Check that the ap does not exist already
1130 if apname in self.confFile.profiles():
1131 error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
1132 del error_dlg
1133 # try again
1134 self.access_points[ apname ] = profile
1135 self.confFile.set_section( apname, profile )
1136 # if it is not in the auto_profile_order add it
1137 if apname not in self.confFile.auto_profile_order:
1138 self.confFile.auto_profile_order.append(apname)
1139 # add to the store
1140 wep = None
1141 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1142 try:
1143 self.confFile.write()
1144 except IOError, (error_number, error_str):
1145 if error_number == errno.ENOENT:
1146 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1147 del error_dlg
1148 else:
1149 raise IOError(error_number, error_str)
1150 # Add AP to the list displayed to user
1151 try:
1152 self.commandQueue.put('pause')
1153 self.commandQueue.join()
1154 self.apQueue.put_nowait( self.access_points[ apname ] )
1155 self.commandQueue.put('scan')
1156 except Queue.Full:
1157 pass
1158 return True
1159 else:
1160 # Did not create new profile
1161 return False
1163 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1164 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1166 #Parameters:
1168 # 'widget' -- gtk.Widget - The widget sending the event.
1170 # 'data' -- tuple - list of arbitrary arguments (not used)
1172 #Returns:
1174 # nothing
1175 def edit_profile( self, widget, data=None ):
1176 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1177 if not selected_iter: return
1178 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1179 if row_start[1] == ' Multiple APs':
1180 apname = make_section_name(row_start[0], '')
1181 else:
1182 apname = make_section_name(row_start[0], row_start[1])
1183 profile = self.confFile.get_profile( apname )
1184 if profile:
1185 profile_editor = profile_dialog( self, profile )
1186 try:
1187 edited_profile = profile_editor.run()
1188 except ValueError:
1189 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1190 del error_dlg
1191 return False
1192 finally:
1193 profile_editor.destroy()
1194 if edited_profile:
1195 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
1196 try:
1197 self.commandQueue.put('pause')
1198 self.commandQueue.join()
1199 except Queue.Full:
1200 pass
1201 self.delete_profile(selected_iter, apname)
1202 apname = make_section_name(profile['essid'], profile['bssid'])
1203 del self.access_points[apname]
1204 self.confFile.remove_section(apname)
1205 # Add AP to the list displayed to user
1206 try:
1207 self.apQueue.put_nowait(edited_profile)
1208 self.commandQueue.put('scan')
1209 except Queue.Full:
1210 pass
1211 apname = make_section_name(edited_profile['essid'], edited_profile['bssid'])
1212 self.access_points[apname] = edited_profile
1213 self.confFile.set_section(apname, edited_profile)
1214 try:
1215 self.confFile.write()
1216 except IOError, (error_number, error_str):
1217 if error_number == errno.ENOENT:
1218 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1219 del error_dlg
1220 else:
1221 raise IOError(error_number, error_str)
1222 else:
1223 profile = get_new_profile()
1224 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1225 self.create_new_profile( widget, profile, data )
1227 # Delete an AP profile (i.e. make profile unknown)
1229 #Parameters:
1231 # 'selected_iter' -- gtk.TreeIter - The selected row.
1233 # 'apname' -- string - The configuration file section to remove
1235 #Returns:
1237 # nothing
1238 def delete_profile(self, selected_iter, apname):
1239 # Remove it
1240 del self.access_points[apname]
1241 self.confFile.remove_section(apname)
1242 self.logger.info(apname)
1243 if apname in self.confFile.auto_profile_order:
1244 self.confFile.auto_profile_order.remove(apname)
1245 self.pstore.remove(selected_iter)
1246 # Let's save our current state
1247 self.update_auto_profile_order()
1248 try:
1249 self.confFile.write()
1250 except IOError, (error_number, error_str):
1251 if error_number == errno.ENOENT:
1252 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1253 del error_dlg
1254 else:
1255 raise IOError(error_number, error_str)
1257 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1258 # Check with user first.
1260 #Parameters:
1262 # 'widget' -- gtk.Widget - The widget sending the event.
1264 # 'data' -- tuple - list of arbitrary arguments (not used)
1266 #Returns:
1268 # nothing
1269 def delete_profile_with_check(self, widget, data=None):
1270 (store, selected_iter) = self.plist.get_selection().get_selected()
1271 if not selected_iter: return
1272 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
1273 known = store.get_value( selected_iter, 1 )
1274 if not known: return
1275 res = dlg.run()
1276 dlg.destroy()
1277 del dlg
1278 if res == gtk.RESPONSE_NO:
1279 return
1280 self.delete_profile(selected_iter, apname)
1282 # Respond to a request to connect to an AP.
1284 #Parameters:
1286 # 'widget' -- gtk.Widget - The widget sending the event.
1288 # 'profile' -- dictionary - The AP profile to which to connect.
1290 # 'data' -- tuple - list of arbitrary arguments (not used)
1292 #Returns:
1294 # nothing
1295 def connect_profile( self, widget, profile, data=None ):
1296 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1297 if not selected_iter: return
1298 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1299 known = store.get_value( selected_iter, 2 )
1300 if not known:
1301 if data != 'noconnect':
1302 dlg = gtk.MessageDialog(
1303 self.window,
1304 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1305 gtk.MESSAGE_QUESTION,
1306 gtk.BUTTONS_YES_NO,
1307 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1308 res = dlg.run()
1309 dlg.destroy()
1310 del dlg
1311 if res == gtk.RESPONSE_NO: return
1312 profile = get_new_profile()
1313 profile['essid'] = essid
1314 profile['bssid'] = bssid
1315 if not self.create_new_profile( widget, profile, data ):
1316 return
1317 else:
1318 # Attempt to match to known profiles, normal then roaming
1319 profile = self.confFile.get_profile(make_section_name(essid, bssid))
1320 if not profile:
1321 profile = self.confFile.get_profile(make_section_name(essid, ''))
1322 profile['bssid'] = self.access_points[apname]['bssid']
1323 self.connection.connect_to_network(profile, self.status_window)
1325 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1327 #Parameters:
1329 # 'widget' -- gtk.Widget - The widget sending the event.
1331 # 'data' -- tuple - list of arbitrary arguments (not used)
1333 #Returns:
1335 # nothing
1336 def disconnect_profile( self, widget, data=None ):
1337 if data == "cancel":
1338 self.status_window.update_message("Canceling connection...")
1339 if sys.modules.has_key("gtk"):
1340 while gtk.events_pending():
1341 gtk.main_iteration(False)
1342 sleep(1)
1343 self.connection.disconnect_interface()
1345 # Update the config file auto profile order from the on-screen order
1347 #Parameters:
1349 # 'widget' -- gtk.Widget - The widget sending the event.
1351 # 'data' -- tuple - list of arbitrary arguments (not used)
1353 # 'data2' -- tuple - list of arbitrary arguments (not used)
1355 #Returns:
1357 # nothing
1358 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1359 # recreate the auto_profile_order
1360 auto_profile_order = []
1361 piter = self.pstore.get_iter_first()
1362 while piter:
1363 # only if it's known
1364 if self.pstore.get_value( piter, 2 ) == True:
1365 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1366 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1367 piter = self.pstore.iter_next( piter )
1368 self.confFile.auto_profile_order = auto_profile_order
1369 try:
1370 self.confFile.write()
1371 except IOError, (error_number, error_str):
1372 if error_number == errno.ENOENT:
1373 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1374 del error_dlg
1375 else:
1376 raise IOError(error_number, error_str)
1379 # Button to allow user to choose a file and put value into specified gtk.Entry
1380 class file_browse_button(gtk.Button):
1381 # Create a button to simulate a File/Open
1383 #Parameters:
1385 # 'parent' -- gtk.Object -- Usually, the calling window.
1387 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1389 #Returns:
1391 # file_browse_button instance
1392 def __init__( self, parent, entry ):
1393 self.parent_window = parent
1394 self.entry = entry
1395 gtk.Button.__init__(self, "Browse", None)
1396 #self.
1397 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)
1398 self.connect("clicked", self.browse_files)
1400 # Show filechooser dialog and get user selection
1402 #Parameters:
1404 # 'widget' -- gtk.Widget -- The widget sending the event.
1406 #Returns:
1408 # nothing
1410 #NOTES:
1412 # updates entry value
1414 def browse_files( self, widget ):
1415 self.browser_dialog.set_filename(self.entry.get_text())
1416 self.browser_dialog.run()
1417 self.entry.set_text(self.browser_dialog.get_filename())
1418 self.browser_dialog.destroy()
1421 # Simple dialog to report an error to the user.
1422 class ErrorDialog:
1423 # Create a new ErrorDialog.
1425 #Parameters:
1427 # 'parent' -- gtk.Object - Usually, the calling window.
1429 # 'message' -- string - The message to display to the user.
1431 #Returns:
1433 # ErrorDialog instance
1434 def __init__( self, parent, message ):
1435 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1436 dialog.run()
1437 dialog.destroy()
1438 del dialog
1441 # The preferences dialog. Edits non-profile sections of the config file.
1442 class preferences_dialog:
1443 # Create a new preferences_dialog.
1445 #Parameters:
1447 # 'parent' -- gtk.Object - Usually, the calling window.
1449 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1451 #Returns:
1453 # preferences_dialog instance
1454 def __init__( self, parent, confFile ):
1455 global wifi_radar_icon
1456 self.parent = parent
1457 self.confFile = confFile
1458 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1459 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1460 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1461 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1462 self.dialog.set_icon( icon )
1463 self.dialog.set_resizable( True )
1464 self.dialog.set_transient_for( self.parent.window )
1465 self.tooltips = gtk.Tooltips()
1467 # set up preferences widgets
1469 # build everything in a tabbed notebook
1470 self.prefs_notebook = gtk.Notebook()
1472 ### General tab
1473 self.general_page = gtk.VBox()
1474 # auto detect wireless device
1475 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1477 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1479 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1480 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1481 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1482 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1484 # network interface selecter
1485 self.w_interface = gtk.combo_box_entry_new_text()
1486 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1487 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1488 for device in wireless_devices:
1489 if device != self.confFile.get_opt('DEFAULT.interface'):
1490 self.w_interface.append_text(device)
1491 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1492 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1493 self.w_interface.set_active(0)
1494 self.w_interface_label = gtk.Label("Wireless device")
1495 self.w_hbox1 = gtk.HBox(False, 0)
1496 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1497 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1498 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1499 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1501 # scan timeout (spin button of integers from 1 to 100)
1502 #self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1503 #self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1504 #self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1505 #self.w_scan_timeout.set_numeric(True)
1506 #self.w_scan_timeout.set_snap_to_ticks(True)
1507 #self.w_scan_timeout.set_wrap(False)
1508 #self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1509 #self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1510 #self.w_hbox2 = gtk.HBox(False, 0)
1511 #self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1512 #self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1513 #self.general_page.pack_start(self.w_hbox2, False, False, 5)
1515 # speak up
1516 self.w_speak_up = gtk.CheckButton("Use speak-up")
1517 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1518 self.w_speak_up.connect("toggled", self.toggle_speak)
1519 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1520 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1522 # speak up command
1523 self.w_speak_cmd = gtk.Entry()
1524 self.w_speak_cmd.set_width_chars(16)
1525 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1526 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1527 self.w_speak_cmd_label = gtk.Label("Speak Command")
1528 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1529 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1530 self.w_hbox3 = gtk.HBox(False, 0)
1531 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1532 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1533 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1534 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1535 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1537 # commit required
1538 self.w_commit_required = gtk.CheckButton("Commit required")
1539 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1540 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1541 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1543 # ifup required
1544 self.w_ifup_required = gtk.CheckButton("Ifup required")
1545 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1546 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1547 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1549 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1550 ### End of General tab
1552 ### Advanced tab
1553 # table to use for layout of following command configurations
1554 self.cmds_table = gtk.Table()
1556 # ifconfig command
1557 self.ifconfig_cmd = gtk.Entry()
1558 self.ifconfig_cmd.set_width_chars(32)
1559 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1560 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1561 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1562 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1563 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1564 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1565 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1566 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1568 # iwconfig command
1569 self.iwconfig_cmd = gtk.Entry()
1570 self.iwconfig_cmd.set_width_chars(32)
1571 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1572 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1573 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1574 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1575 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1576 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1577 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1578 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1580 # iwlist command
1581 self.iwlist_cmd = gtk.Entry()
1582 self.iwlist_cmd.set_width_chars(32)
1583 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1584 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1585 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1586 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1587 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1588 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1589 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1590 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1592 # route command
1593 self.route_cmd = gtk.Entry()
1594 self.route_cmd.set_width_chars(32)
1595 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1596 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1597 self.route_cmd_label = gtk.Label("Network route configure command")
1598 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1599 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1600 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1601 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1602 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1604 # log file
1605 self.logfile_entry = gtk.Entry()
1606 self.logfile_entry.set_width_chars(32)
1607 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1608 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1609 self.logfile_label = gtk.Label("Log file")
1610 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1611 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1612 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, True, False, 5, 0)
1613 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, True, False, 0, 0)
1614 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, False, False, 0, 0)
1616 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1617 ### End of Advanced tab
1619 ### DHCP tab
1620 # table to use for layout of DHCP prefs
1621 self.dhcp_table = gtk.Table()
1623 self.dhcp_cmd = gtk.Entry()
1624 self.dhcp_cmd.set_width_chars(32)
1625 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1626 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1627 self.dhcp_cmd_label = gtk.Label("Command")
1628 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1629 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1630 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1631 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1633 self.dhcp_args = gtk.Entry()
1634 self.dhcp_args.set_width_chars(32)
1635 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1636 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1637 self.dhcp_args_label = gtk.Label("Arguments")
1638 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1639 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1641 self.dhcp_kill_args = gtk.Entry()
1642 self.dhcp_kill_args.set_width_chars(32)
1643 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1644 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1645 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1646 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1647 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1649 self.dhcp_timeout = gtk.Entry()
1650 self.dhcp_timeout.set_width_chars(32)
1651 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1652 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1653 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1654 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1655 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1657 self.dhcp_pidfile = gtk.Entry()
1658 self.dhcp_pidfile.set_width_chars(32)
1659 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1660 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1661 self.dhcp_pidfile_label = gtk.Label("PID file")
1662 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1663 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1665 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1666 ### End of DHCP tab
1668 ### WPA tab
1669 # table to use for layout of DHCP prefs
1670 self.wpa_table = gtk.Table()
1672 self.wpa_cmd = gtk.Entry()
1673 self.wpa_cmd.set_width_chars(32)
1674 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1675 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1676 self.wpa_cmd_label = gtk.Label("Command")
1677 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1678 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1679 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1680 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1682 self.wpa_args = gtk.Entry()
1683 self.wpa_args.set_width_chars(32)
1684 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1685 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1686 self.wpa_args_label = gtk.Label("Arguments")
1687 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1688 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1690 self.wpa_kill_args = gtk.Entry()
1691 self.wpa_kill_args.set_width_chars(32)
1692 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1693 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1694 self.wpa_kill_args_label = gtk.Label("Kill command")
1695 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1696 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1698 self.wpa_config = gtk.Entry()
1699 self.wpa_config.set_width_chars(32)
1700 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1701 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1702 self.wpa_config_label = gtk.Label("Configuration file")
1703 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1704 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1706 self.wpa_driver = gtk.Entry()
1707 self.wpa_driver.set_width_chars(32)
1708 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1709 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1710 self.wpa_driver_label = gtk.Label("Driver")
1711 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1712 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1714 self.wpa_pidfile = gtk.Entry()
1715 self.wpa_pidfile.set_width_chars(32)
1716 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1717 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1718 self.wpa_pidfile_label = gtk.Label("PID file")
1719 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1720 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1722 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1723 ### End of WPA tab
1725 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1727 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1729 #Parameters:
1731 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1733 # 'data' -- tuple - list of arbitrary arguments (not used)
1735 #Returns:
1737 # nothing
1738 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1739 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1741 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1743 #Parameters:
1745 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1747 # 'data' -- tuple - list of arbitrary arguments (not used)
1749 #Returns:
1751 # nothing
1752 def toggle_speak(self, speak_toggle, data=None):
1753 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1754 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1756 # Display preferences dialog and operate until canceled or okayed.
1758 #Parameters:
1760 # nothing
1762 #Returns:
1764 # integer -- gtk response ID
1765 def run(self):
1766 self.dialog.show_all()
1767 return self.dialog.run()
1769 # Write updated values to config file.
1771 #Parameters:
1773 # nothing
1775 #Returns:
1777 # nothing
1778 def save(self):
1779 if self.w_auto_detect.get_active():
1780 set_network_device("auto_detect")
1781 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1782 else:
1783 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1784 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1785 self.confFile.set_opt('DEFAULT.interface', interface)
1786 set_network_device(interface)
1787 #self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1788 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1789 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1790 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1791 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1792 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1793 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1794 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1795 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1796 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1797 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1798 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1799 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1800 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1801 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1802 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1803 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1804 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1805 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1806 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1807 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1808 try:
1809 self.confFile.write()
1810 except IOError, (error_number, error_str):
1811 if error_number == errno.ENOENT:
1812 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1813 del error_dlg
1814 else:
1815 raise IOError(error_number, error_str)
1817 # Remove preferences window.
1819 #Parameters:
1821 # nothing
1823 #Returns:
1825 # nothing
1826 def destroy(self):
1827 self.dialog.destroy()
1828 del self.dialog
1831 # Edit and return an AP profile.
1832 class profile_dialog:
1833 # Create a new profile_dialog.
1835 #Parameters:
1837 # 'parent' -- gtk.Object - Usually, the calling window.
1839 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1841 #Returns:
1843 # profile_dialog instance
1844 def __init__( self, parent, profile ):
1845 global wifi_radar_icon
1847 # Labels
1848 self.WIFI_SET_LABEL = "WiFi Options"
1849 self.USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
1850 self.USE_IP_LABEL = "Manual network configuration"
1851 self.USE_WPA_LABEL = "Use WPA"
1852 self.NO_WPA_LABEL = "No WPA"
1853 self.CON_PP_LABEL = "Connection Commands"
1854 self.DIS_PP_LABEL = "Disconnection Commands"
1856 self.parent = parent
1857 self.profile = profile.copy()
1858 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1859 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1860 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1861 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1862 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1863 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1864 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1865 self.dialog.set_icon( icon )
1866 self.dialog.set_resizable( False )
1867 self.dialog.set_transient_for( self.parent.window )
1868 #self.dialog.set_size_request( 400, 400 )
1869 #################
1870 self.tooltips = gtk.Tooltips()
1872 general_table = gtk.Table()
1873 general_table.set_row_spacings(3)
1874 general_table.set_col_spacings(3)
1875 # The essid labels
1876 general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
1877 # The essid textboxes
1878 self.essid_entry = gtk.Entry(32)
1879 self.essid_entry.set_text(self.profile['essid'])
1880 general_table.attach(self.essid_entry, 1, 2, 0, 1)
1881 # Add the essid table to the dialog
1882 self.dialog.vbox.pack_start(general_table, True, True, 5)
1883 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
1885 # The bssid labels
1886 general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
1887 # The bssid textboxes
1888 self.bssid_entry = gtk.Entry(32)
1889 self.bssid_entry.set_text(self.profile['bssid'])
1890 self.bssid_entry.set_sensitive(not self.profile['roaming'])
1891 general_table.attach(self.bssid_entry, 1, 2, 1, 2)
1892 # Add the bssid table to the dialog
1893 self.dialog.vbox.pack_start(general_table, True, True, 5)
1894 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
1895 # Add the roaming checkbox
1896 self.roaming_cb = gtk.CheckButton('Roaming')
1897 self.roaming_cb.set_active(self.profile['roaming'])
1898 general_table.attach(self.roaming_cb, 1, 2, 1, 2)
1899 self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
1900 # create the WiFi expander
1901 self.wifi_expander = gtk.Expander( self.WIFI_SET_LABEL )
1902 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1903 wifi_table = gtk.Table( 4, 2, False )
1904 wifi_table.set_row_spacings( 3 )
1905 wifi_table.set_col_spacings( 3 )
1906 # The WiFi labels
1907 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1908 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1909 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1910 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1911 # The WiFi text boxes
1912 self.mode_combo = gtk.combo_box_new_text()
1913 for mode in self.WIFI_MODES:
1914 self.mode_combo.append_text( mode )
1915 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1916 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1917 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
1918 self.channel_combo = gtk.combo_box_new_text()
1919 for channel in self.WIFI_CHANNELS:
1920 self.channel_combo.append_text( channel )
1921 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1922 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1923 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
1925 self.key_entry = gtk.Entry( 64 )
1926 self.key_entry.set_text( self.profile['key'] )
1927 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1928 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
1930 self.security_combo = gtk.combo_box_new_text()
1931 for security in self.WIFI_SECURITY:
1932 self.security_combo.append_text( security )
1933 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1934 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1935 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
1936 # Add the wifi table to the expander
1937 self.wifi_expander.add( wifi_table )
1938 # Add the expander to the dialog
1939 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1941 # create the wpa expander
1942 self.wpa_expander = gtk.Expander( self.NO_WPA_LABEL )
1943 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1944 wpa_table = gtk.Table( 1, 2, False )
1945 wpa_table.set_row_spacings( 3 )
1946 wpa_table.set_col_spacings( 3 )
1947 # The labels
1948 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1949 # The text boxes
1950 self.wpa_driver_entry = gtk.Entry()
1951 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1952 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1953 # Add the wpa table to the expander
1954 self.wpa_expander.add( wpa_table )
1955 # Add the expander to the dialog
1956 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1958 # create the dhcp expander
1959 self.dhcp_expander = gtk.Expander( self.USE_DHCP_LABEL )
1960 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1961 ip_table = gtk.Table( 6, 2, False )
1962 ip_table.set_row_spacings( 3 )
1963 ip_table.set_col_spacings( 3 )
1964 # The IP labels
1965 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1966 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1967 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1968 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1969 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1970 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1971 # The IP text boxes
1972 self.ip_entry = gtk.Entry( 15 )
1973 self.ip_entry.set_text( self.profile['ip'] )
1974 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1975 self.netmask_entry = gtk.Entry( 15 )
1976 self.netmask_entry.set_text( self.profile['netmask'] )
1977 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1978 self.gw_entry = gtk.Entry( 15 )
1979 self.gw_entry.set_text( self.profile['gateway'] )
1980 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1981 self.domain_entry = gtk.Entry( 32 )
1982 self.domain_entry.set_text( self.profile['domain'] )
1983 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1984 self.dns1_entry = gtk.Entry( 15 )
1985 self.dns1_entry.set_text( self.profile['dns1'] )
1986 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1987 self.dns2_entry = gtk.Entry( 15 )
1988 self.dns2_entry.set_text( self.profile['dns2'] )
1989 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1990 # Add the ip table to the expander
1991 self.dhcp_expander.add( ip_table )
1992 # Add the expander to the dialog
1993 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1995 # create the connection-building postpre expander
1996 self.con_pp_expander = gtk.Expander( self.CON_PP_LABEL )
1997 con_pp_table = gtk.Table( 2, 2, False )
1998 con_pp_table.set_row_spacings( 3 )
1999 con_pp_table.set_col_spacings( 3 )
2000 # The labels
2001 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2002 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2003 # The text boxes
2004 self.con_prescript_entry = gtk.Entry()
2005 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
2006 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
2007 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
2008 self.con_postscript_entry = gtk.Entry()
2009 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
2010 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
2011 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
2012 # Add the pp table to the expander
2013 self.con_pp_expander.add( con_pp_table )
2014 # Add the expander to the dialog
2015 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
2017 # create the disconnection postpre expander
2018 self.dis_pp_expander = gtk.Expander( self.DIS_PP_LABEL )
2019 dis_pp_table = gtk.Table( 2, 2, False )
2020 dis_pp_table.set_row_spacings( 3 )
2021 dis_pp_table.set_col_spacings( 3 )
2022 # The labels
2023 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2024 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2025 # The text boxes
2026 self.dis_prescript_entry = gtk.Entry()
2027 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
2028 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
2029 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
2030 self.dis_postscript_entry = gtk.Entry()
2031 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
2032 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
2033 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
2034 # Add the pp table to the expander
2035 self.dis_pp_expander.add( dis_pp_table )
2036 # Add the expander to the dialog
2037 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
2039 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
2041 #Parameters:
2043 # nothing
2045 #Returns:
2047 # dictionary or None -- a profile, or None on cancel
2049 #NOTES:
2051 # Raises ValueError if an attempt is made to save an ESSID with no name.
2052 def run( self ):
2053 self.dialog.show_all()
2054 if self.dialog.run():
2055 if self.essid_entry.get_text().strip() == "":
2056 raise ValueError
2057 self.profile['known'] = True
2058 self.profile['essid'] = self.essid_entry.get_text().strip()
2059 self.profile['bssid'] = self.bssid_entry.get_text().strip()
2060 self.profile['roaming'] = self.roaming_cb.get_active()
2061 self.profile['key'] = self.key_entry.get_text().strip()
2062 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
2063 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
2064 self.profile['encrypted'] = ( self.profile['security'] != '' )
2065 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
2066 self.profile['protocol'] = 'g'
2067 self.profile['available'] = ( self.profile['signal'] > 0 )
2068 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
2069 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
2070 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
2071 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
2072 # wpa
2073 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
2074 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
2075 # dhcp
2076 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
2077 self.profile['ip'] = self.ip_entry.get_text().strip()
2078 self.profile['netmask'] = self.netmask_entry.get_text().strip()
2079 self.profile['gateway'] = self.gw_entry.get_text().strip()
2080 self.profile['domain'] = self.domain_entry.get_text().strip()
2081 self.profile['dns1'] = self.dns1_entry.get_text().strip()
2082 self.profile['dns2'] = self.dns2_entry.get_text().strip()
2083 return self.profile
2084 return None
2086 # Remove profile dialog.
2088 #Parameters:
2090 # nothing
2092 #Returns:
2094 # nothing
2095 def destroy( self ):
2096 self.dialog.destroy()
2097 del self.dialog
2099 # Respond to expanding/hiding IP segment.
2101 #Parameters:
2103 # 'widget' -- gtk.Widget - The widget sending the event.
2105 # 'data' -- tuple - List of arbitrary arguments (not used)
2107 #Returns:
2109 # nothing
2110 def toggle_use_dhcp( self, widget, data = None ):
2111 expanded = self.dhcp_expander.get_expanded()
2112 if expanded:
2113 self.dhcp_expander.set_label( self.USE_IP_LABEL )
2114 else:
2115 self.dhcp_expander.set_label( self.USE_DHCP_LABEL )
2117 # Respond to expanding/hiding WPA segment.
2119 #Parameters:
2121 # 'widget' -- gtk.Widget - The widget sending the event.
2123 # 'data' -- tuple - List of arbitrary arguments (not used)
2125 #Returns:
2127 # nothing
2128 def toggle_use_wpa( self, widget, data = None ):
2129 expanded = self.wpa_expander.get_expanded()
2130 if expanded:
2131 self.wpa_expander.set_label( self.USE_WPA_LABEL )
2132 else:
2133 self.wpa_expander.set_label( self.NO_WPA_LABEL )
2135 # Return the index where item matches a cell in array.
2137 #Parameters:
2139 # 'item' -- string - Item to find in array
2141 # 'array' -- list - List in which to find match.
2143 #Returns:
2145 # integer - 0 (no match) or higher (index of match)
2146 def get_array_index( self, item, array ):
2147 try:
2148 return array.index( item.strip() )
2149 except:
2150 pass
2151 return 0
2153 # Return the value in array[ index ]
2155 #Parameters:
2157 # 'index' -- integer - The index to look up.
2159 # 'array' -- list - List in which to look up value.
2161 #Returns:
2163 # string -- empty string (no match) or looked up value
2164 def get_array_item( self, index, array ):
2165 try:
2166 return array[ index ]
2167 except:
2168 pass
2169 return ''
2172 # A simple class for putting up a "Please wait" dialog so the user
2173 # doesn't think we've forgotten about them. Implements the status interface.
2174 class StatusWindow:
2175 # Create a new StatusWindow.
2177 #Parameters:
2179 # 'parent' -- gtk.Object - Usually, the calling window.
2181 #Returns:
2183 # StatusWindow instance
2185 #NOTE:
2187 # Sample implementation of status interface. Status interface
2188 #requires .show(), .update_message(message), and .hide() methods.
2189 def __init__( self, parent ):
2190 global wifi_radar_icon
2191 self.parent = parent
2192 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2193 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2194 self.dialog.set_icon( icon )
2195 self.lbl = gtk.Label("Please wait...")
2196 self.bar = gtk.ProgressBar()
2197 self.dialog.vbox.pack_start(self.lbl)
2198 self.dialog.vbox.pack_start(self.bar)
2199 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2200 self.timer = None
2202 # Change the message displayed to the user.
2204 #Parameters:
2206 # 'message' -- string - The message to show to the user.
2208 #Returns:
2210 # nothing
2211 def update_message( self, message ):
2212 self.lbl.set_text(message)
2214 # Update the StatusWindow progress bar.
2216 #Parameters:
2218 # nothing
2220 #Returns:
2222 # True -- always return True
2223 def update_window( self ):
2224 self.bar.pulse()
2225 return True
2227 # Display and operate the StatusWindow.
2229 #Parameters:
2231 # nothing
2233 #Returns:
2235 # nothing
2236 def run( self ):
2237 pass
2239 # Show all the widgets of the StatusWindow.
2241 #Parameters:
2243 # nothing
2245 #Returns:
2247 # nothing
2248 def show( self ):
2249 self.dialog.show_all()
2250 self.timer = gobject.timeout_add(250, self.update_window)
2251 return False
2253 # Hide all the widgets of the StatusWindow.
2255 #Parameters:
2257 # nothing
2259 #Returns:
2261 # nothing
2262 def hide( self ):
2263 if self.timer:
2264 gobject.source_remove(self.timer)
2265 self.timer = None
2266 self.dialog.hide_all()
2267 return False
2269 # Remove the StatusWindow.
2271 #Parameters:
2273 # nothing
2275 #Returns:
2277 # nothing
2278 def destroy( self ):
2279 if self.timer:
2280 gobject.source_remove(self.timer)
2281 self.dialog.destroy()
2282 del self.dialog
2285 # Manage a GTK About Dialog
2286 class about_dialog(gtk.AboutDialog):
2287 # Subclass GTK AboutDialog
2289 #Parameters:
2291 # nothing
2293 #Returns:
2295 # nothing
2296 def __init__( self ):
2297 global wifi_radar_icon
2299 gtk.AboutDialog.__init__(self)
2300 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"])
2301 self.set_comments("WiFi connection manager")
2302 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2303 self.set_documenters(["Gary Case"])
2304 license = """
2305 This program is free software; you can redistribute it and/or modify
2306 it under the terms of the GNU General Public License as published by
2307 the Free Software Foundation; either version 2 of the License, or
2308 (at your option) any later version.
2310 This program is distributed in the hope that it will be useful,
2311 but WITHOUT ANY WARRANTY; without even the implied warranty of
2312 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2313 GNU General Public License for more details.
2315 You should have received a copy of the GNU General Public License
2316 along with this program; if not, write to the Free Software
2317 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2318 self.set_license(license)
2319 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2320 self.set_logo(logo)
2321 self.set_name("WiFi Radar")
2322 self.set_version(WIFI_RADAR_VERSION)
2323 self.set_website("http://wifi-radar.berlios.de")
2327 # Manage the configuration for the application, including reading and writing the config from/to a file.
2328 class ConfigFile(ConfigParser.SafeConfigParser):
2329 # Create a new ConfigFile.
2331 #Parameters:
2333 # 'filename' -- string - The configuration file's name.
2335 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2337 #Returns:
2339 # ConfigFile instance
2340 def __init__( self, filename, defaults, raw=False ):
2341 self.filename = filename
2342 self.raw = raw
2343 self.auto_profile_order = []
2344 ConfigParser.SafeConfigParser.__init__(self, defaults)
2346 # Set the contents of a section to values from a dictionary.
2348 #Parameters:
2350 # 'section_name' -- string - Configuration file section.
2352 # 'section_dict' -- dictionary - Values to add to section.
2354 #Returns:
2356 # nothing
2357 def set_section( self, section_name, section_dict ):
2358 try:
2359 self.add_section(section_name)
2360 except ConfigParser.DuplicateSectionError:
2361 pass
2362 for key in section_dict.keys():
2363 if type(section_dict[key]) == BooleanType:
2364 self.set_bool_opt(section_name + "." + key, section_dict[key])
2365 elif type(section_dict[key]) == IntType:
2366 self.set_int_opt(section_name + "." + key, section_dict[key])
2367 elif type(section_dict[key]) == FloatType:
2368 self.set_float_opt(section_name + "." + key, section_dict[key])
2369 else:
2370 self.set_opt(section_name + "." + key, section_dict[key])
2372 # Return the profile recorded in the specified section.
2374 #Parameters:
2376 # 'section_name' -- string - Configuration file section.
2378 #Returns:
2380 # dictionary or None - The specified profile or None if not found
2381 def get_profile( self, section_name ):
2382 if section_name in self.profiles():
2383 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' ]
2384 bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
2385 int_types = [ 'signal' ]
2386 profile = get_new_profile()
2387 for option in bool_types:
2388 option_tmp = self.get_opt_as_bool(section_name + "." + option)
2389 if option_tmp:
2390 profile[option] = option_tmp
2391 for option in int_types:
2392 option_tmp = self.get_opt_as_int(section_name + "." + option)
2393 if option_tmp:
2394 profile[option] = option_tmp
2395 for option in str_types:
2396 option_tmp = self.get_opt(section_name + "." + option)
2397 if option_tmp:
2398 profile[option] = option_tmp
2399 return profile
2400 return None
2402 # Get a config option and handle exceptions.
2404 #Parameters:
2406 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2407 # period and the option key. (E.g. "DEFAULT.interface")
2409 #Returns:
2411 # string or None - option value as string or None on failure
2412 def get_opt( self, option_path ):
2413 #print "ConfigFile.get_opt: ", option_path
2414 (section, option) = option_path.split('.')
2415 try:
2416 return self.get(section, option, self.raw)
2417 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2418 return None
2420 # Get a config option and return as a boolean type.
2422 #Parameters:
2424 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2425 # period and the option key. (E.g. "DEFAULT.interface")
2427 #Returns:
2429 # boolean - option value as boolean
2430 def get_opt_as_bool( self, option_path ):
2431 option = self.get_opt(option_path)
2432 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2433 return option
2434 if option == 'True':
2435 return True
2436 if option == 'False':
2437 return False
2438 raise ValueError, 'boolean option was not True or False'
2440 # Get a config option and return as an integer type.
2442 #Parameters:
2444 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2445 # period and the option key. (E.g. "DEFAULT.interface")
2447 #Returns:
2449 # integer- option value as integer
2450 def get_opt_as_int( self, option_path ):
2451 return int(float(self.get_opt(option_path)))
2453 # Convert boolean type to string and set config option.
2455 #Parameters:
2457 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2458 # period and the option key. (E.g. "DEFAULT.interface")
2460 # 'value' -- boolean - Value to set.
2462 #Returns:
2464 # nothing
2465 def set_bool_opt( self, option_path, value ):
2466 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2467 value == 'True'
2468 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2469 value == 'False'
2470 else:
2471 raise ValueError, 'cannot convert value to string'
2472 self.set_opt(option_path, repr(value))
2474 # Convert integer type to string and set config option.
2476 #Parameters:
2478 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2479 # period and the option key. (E.g. "DEFAULT.interface")
2481 # 'value' -- integer - Value to set.
2483 #Returns:
2485 # nothing
2486 def set_int_opt( self, option_path, value ):
2487 if not isinstance(value, IntType):
2488 raise ValueError, 'value is not an integer'
2489 self.set_opt(option_path, repr(value))
2491 # Convert float type to string and set config option.
2493 #Parameters:
2495 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2496 # period and the option key. (E.g. "DEFAULT.interface")
2498 # 'value' -- float - Value to set.
2500 #Returns:
2502 # nothing
2503 def set_float_opt( self, option_path, value ):
2504 if not isinstance(value, FloatType):
2505 raise ValueError, 'value is not a float'
2506 self.set_opt(option_path, repr(int(value)))
2508 # Set a config option while handling exceptions.
2510 #Parameters:
2512 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2513 # period and the option key. (E.g. "DEFAULT.interface")
2515 # 'value' -- string - Value to set.
2517 #Returns:
2519 # nothing
2520 def set_opt( self, option_path, value ):
2521 (section, option) = option_path.split('.')
2522 try:
2523 self.set(section, option, value)
2524 except ConfigParser.NoSectionError:
2525 self.add_section(section)
2526 self.set_opt(option_path, value)
2528 # Return a list of the section names which denote AP profiles.
2530 #Parameters:
2532 # nothing
2534 #Returns:
2536 # list - profile names
2537 def profiles( self ):
2538 profile_list = []
2539 for section in self.sections():
2540 if ':' in section:
2541 profile_list.append(section)
2542 return profile_list
2544 # Read configuration file from disk into instance variables.
2546 #Parameters:
2548 # nothing
2550 #Returns:
2552 # nothing
2553 def read( self ):
2554 fp = open( self.filename, "r" )
2555 self.readfp(fp)
2556 # convert the auto_profile_order to a list for ordering
2557 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2558 for ap in self.profiles():
2559 self.set_bool_opt( ap + '.known', True)
2560 if ap in self.auto_profile_order: continue
2561 self.auto_profile_order.append( ap )
2562 fp.close()
2564 # Write configuration file to disk from instance variables. Copied from
2565 # ConfigParser and modified to write options in alphabetical order.
2567 #Parameters:
2569 # nothing
2571 #Returns:
2573 # nothing
2574 def write( self ):
2575 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2576 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2577 fp = open( self.filename, "w" )
2578 # write DEFAULT section first
2579 if self._defaults:
2580 fp.write("[DEFAULT]\n")
2581 for key in sorted(self._defaults.keys()):
2582 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2583 fp.write("\n")
2584 # write non-profile sections first
2585 for section in self._sections:
2586 if section not in self.profiles():
2587 fp.write("[%s]\n" % section)
2588 for key in sorted(self._sections[section].keys()):
2589 if key != "__name__":
2590 fp.write("%s = %s\n" %
2591 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2592 fp.write("\n")
2593 # write profile sections
2594 for section in self._sections:
2595 if section in self.profiles():
2596 fp.write("[%s]\n" % section)
2597 for key in sorted(self._sections[section].keys()):
2598 if key != "__name__":
2599 fp.write("%s = %s\n" %
2600 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2601 fp.write("\n")
2602 fp.close()
2606 # Load our conf file and known profiles
2607 # Defaults, these may get overridden by values found in the conf file.
2608 config_defaults = { # The network interface you use.
2609 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2610 'interface': "auto_detect",
2611 # How long should the scan for access points last?
2612 #'scan_timeout': '5',
2613 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2614 # Set the speak_up option to false if you do not have or want this.
2615 'speak_command': '/usr/bin/say',
2616 # Should I speak up when connecting to a network? (If you have a speech command)
2617 'speak_up': 'False',
2618 # You may set this to true for cards that require a "commit" command with iwconfig
2619 'commit_required': 'False',
2620 # You may set this to true for cards that require the interface to be brought up first
2621 'ifup_required': 'False',
2622 # set the location of the log file
2623 'logfile': './wifi-radar.log',
2624 # Set the location of several important programs
2625 'iwlist_command': '/sbin/iwlist',
2626 'iwconfig_command': '/sbin/iwconfig',
2627 'ifconfig_command': '/sbin/ifconfig',
2628 'route_command': '/sbin/route',
2629 'auto_profile_order': '[]',
2630 'version': WIFI_RADAR_VERSION }
2632 config_dhcp = { # DHCP client
2633 'command': '/sbin/dhcpcd',
2634 # How long to wait for an IP addr from DHCP server
2635 'timeout': '30',
2636 # Arguments to use with DHCP client on connect
2637 'args': '-D -o -i dhcp_client -t %(timeout)s',
2638 # Argument to use with DHCP client on disconnect
2639 'kill_args': '-k',
2640 # The file where DHCP client PID is written
2641 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2643 config_wpa = { # WPA Supplicant
2644 'command': '/usr/sbin/wpa_supplicant',
2645 # Arguments to use with WPA Supplicant on connect
2646 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2647 # Arguments to use with WPA Supplicant on disconnect
2648 'kill_command': '',
2649 # Where the WPA Supplicant config file can be found
2650 'configuration': '/etc/wpa_supplicant.conf',
2651 # Driver to use with WPA Supplicant
2652 'driver': 'wext',
2653 # The file where WPA Supplicant PID is written
2654 'pidfile': '/var/run/wpa_supplicant.pid' }
2656 # initialize config, with defaults
2657 confFile = ConfigFile(CONF_FILE, config_defaults)
2658 confFile.set_section("DHCP", config_dhcp)
2659 confFile.set_section("WPA", config_wpa)
2661 if not os.path.isfile( CONF_FILE ):
2662 confFile.set_bool_opt('DEFAULT.new_file', True)
2663 else:
2664 if not os.access(CONF_FILE, os.R_OK):
2665 print "Can't open " + CONF_FILE + "."
2666 print "Are you root?"
2667 sys.exit()
2668 confFile.read()
2671 ####################################################################################################
2672 # Embedded Images
2673 wifi_radar_icon = [ ""
2674 "GdkP"
2675 "\0\0\22""7"
2676 "\2\1\0\2"
2677 "\0\0\1\214"
2678 "\0\0\0c"
2679 "\0\0\0O"
2680 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2681 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2682 "\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"
2683 "\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"
2684 "\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"
2685 "\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"
2686 "\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"
2687 "\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"
2688 "\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"
2689 "\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"
2690 "\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"
2691 "\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"
2692 "\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"
2693 "\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"
2694 "\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"
2695 "\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"
2696 "\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"
2697 "\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"
2698 "\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"
2699 "\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"
2700 "\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"
2701 "\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"
2702 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2703 "\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"
2704 "\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"
2705 "\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"
2706 "\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"
2707 "\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"
2708 "\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"
2709 "\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"
2710 "\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"
2711 "\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"
2712 "\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"
2713 "\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"
2714 "\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"
2715 "\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"
2716 "\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"
2717 "\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"
2718 "\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"
2719 "\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"
2720 "\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"
2721 "\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"
2722 "\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"
2723 "\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"
2724 "\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"
2725 "\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"
2726 "\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"
2727 "\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"
2728 "\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"
2729 "\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"
2730 "\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"
2731 "\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"
2732 "\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"
2733 "\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"
2734 "\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"
2735 "\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"
2736 "\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"
2737 "\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"
2738 "\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"
2739 "\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"
2740 "\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"
2741 "\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"
2742 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2743 "\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"
2744 "\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"
2745 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2746 "\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"
2747 "\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"
2748 "\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"
2749 "\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"
2750 "\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"
2751 "\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"
2752 "\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"
2753 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2754 "\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"
2755 "\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"
2756 "\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"
2757 "\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"
2758 "\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"
2759 "\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"
2760 "|\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"
2761 "\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"
2762 "\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"
2763 "\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"
2764 "\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"
2765 "\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"
2766 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2767 "\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"
2768 "\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"
2769 "\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"
2770 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2771 "\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"
2772 "\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"
2773 "\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"
2774 "\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"
2775 "\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"
2776 "\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"
2777 "\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"
2778 "\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"
2779 "\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"
2780 "\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"
2781 "\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"
2782 "\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"
2783 "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"
2784 "\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"
2785 "\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"
2786 "\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"
2787 "\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"
2788 "\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"
2789 "\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"
2790 "\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"
2791 "\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"
2792 "\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"
2793 "\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"
2794 "\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"
2795 "\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"
2796 "\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"
2797 "\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"
2798 "\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|"
2799 "\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"
2800 "\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"
2801 "\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"
2802 "\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"
2803 "\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"
2804 "\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"
2805 "\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"
2806 "\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"
2807 "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"
2808 "\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"
2809 "\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"
2810 "\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"
2811 "\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"
2812 "\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"
2813 "\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"
2814 "\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"
2815 "\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"
2816 "\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"
2817 "\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"
2818 "\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"
2819 "\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"
2820 "\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"
2821 "\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"
2822 "\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"
2823 "\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"
2824 "\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"
2825 "\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"
2826 "\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"
2827 "\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"
2828 "\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"
2829 "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"
2830 "\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"
2831 "\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"
2832 "\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"
2833 "\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"
2834 "\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"
2835 "\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"
2836 "\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"
2837 "\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"
2838 "\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"
2839 "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"
2840 "\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"
2841 "\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"
2842 "\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"
2843 "\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"
2844 "\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"
2845 "\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"
2846 "\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"
2847 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2848 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2849 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2850 "\0"]
2852 known_profile_icon = [ ""
2853 "GdkP"
2854 "\0\0\5""0"
2855 "\2\1\0\2"
2856 "\0\0\0P"
2857 "\0\0\0\24"
2858 "\0\0\0\24"
2859 "\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"
2860 "\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"
2861 "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"
2862 "\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"
2863 "\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"
2864 "\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"
2865 "\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"
2866 "\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"
2867 "\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"
2868 "\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"
2869 "\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"
2870 "\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"
2871 "\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"
2872 "\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"
2873 "\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"
2874 "\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"
2875 "\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"
2876 "\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"
2877 "\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"
2878 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2879 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2880 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2881 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2882 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2883 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2884 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2885 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2886 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2887 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2888 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2889 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2890 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2891 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2892 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2893 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2894 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2895 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2896 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2897 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2898 "\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"
2899 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2900 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2901 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2902 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2903 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2904 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2905 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2906 "\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"
2907 "\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"
2908 "\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"
2909 "\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"
2910 "\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"]
2912 unknown_profile_icon = [ ""
2913 "GdkP"
2914 "\0\0\5\22"
2915 "\2\1\0\2"
2916 "\0\0\0P"
2917 "\0\0\0\24"
2918 "\0\0\0\24"
2919 "\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"
2920 "\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"
2921 "\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"
2922 "\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"
2923 "(\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"
2924 "\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"
2925 "#\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"
2926 "\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"
2927 "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"
2928 "\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"
2929 "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"
2930 "\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"
2931 "\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"
2932 "\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"
2933 "\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"
2934 "\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"
2935 "\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"
2936 "\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"
2937 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2938 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2939 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2940 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2941 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2942 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2943 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2944 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2945 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2946 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2947 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2948 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2949 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2950 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2951 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2952 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2953 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2954 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2955 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2956 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2957 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2958 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2959 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2960 "\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"
2961 "\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"
2962 "\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"
2963 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2965 signal_xpm_barely = [
2966 "20 20 10 1",
2967 " c None",
2968 ". c #C6C6C6",
2969 "+ c #CCCCCC",
2970 "@ c #DBDBDB",
2971 "# c #D3D3D3",
2972 "$ c #A9B099",
2973 "% c #95A173",
2974 "& c #6B8428",
2975 "* c #B4B7AC",
2976 "= c #80924D",
2977 " .+++.",
2978 " +@@@+",
2979 " +@@@+",
2980 " +@@@+",
2981 " +@@@+",
2982 " .++++#@@@+",
2983 " +@@@@@@@@+",
2984 " +@@@@@@@@+",
2985 " +@@@@@@@@+",
2986 " +@@@@@@@@+",
2987 " $%%%%#@@@@@@@@+",
2988 " %&&&&@@@@@@@@@+",
2989 " %&&&&@@@@@@@@@+",
2990 " %&&&&@@@@@@@@@+",
2991 " %&&&&@@@@@@@@@+",
2992 "*%%%%=&&&&@@@@@@@@@+",
2993 "%&&&&&&&&&@@@@@@@@@+",
2994 "%&&&&&&&&&@@@@@@@@@+",
2995 "%&&&&&&&&&@@@@@@@@@+",
2996 "*%%%%%%%%%+++++++++."
3000 signal_xpm_best = [
3001 "20 20 6 1",
3002 " c None",
3003 ". c #9DAABF",
3004 "+ c #7B96BF",
3005 "@ c #386EBF",
3006 "# c #5982BF",
3007 "$ c #AEB4BF",
3008 " .+++.",
3009 " +@@@+",
3010 " +@@@+",
3011 " +@@@+",
3012 " +@@@+",
3013 " .++++#@@@+",
3014 " +@@@@@@@@+",
3015 " +@@@@@@@@+",
3016 " +@@@@@@@@+",
3017 " +@@@@@@@@+",
3018 " .++++#@@@@@@@@+",
3019 " +@@@@@@@@@@@@@+",
3020 " +@@@@@@@@@@@@@+",
3021 " +@@@@@@@@@@@@@+",
3022 " +@@@@@@@@@@@@@+",
3023 "$++++#@@@@@@@@@@@@@+",
3024 "+@@@@@@@@@@@@@@@@@@+",
3025 "+@@@@@@@@@@@@@@@@@@+",
3026 "+@@@@@@@@@@@@@@@@@@+",
3027 "$++++++++++++++++++."
3030 signal_xpm_none = [
3031 "20 20 6 1",
3032 " c None",
3033 ". c #C6C6C6",
3034 "+ c #CCCCCC",
3035 "@ c #DBDBDB",
3036 "# c #D3D3D3",
3037 "$ c #C2C2C2",
3038 " .+++.",
3039 " +@@@+",
3040 " +@@@+",
3041 " +@@@+",
3042 " +@@@+",
3043 " .++++#@@@+",
3044 " +@@@@@@@@+",
3045 " +@@@@@@@@+",
3046 " +@@@@@@@@+",
3047 " +@@@@@@@@+",
3048 " .++++#@@@@@@@@+",
3049 " +@@@@@@@@@@@@@+",
3050 " +@@@@@@@@@@@@@+",
3051 " +@@@@@@@@@@@@@+",
3052 " +@@@@@@@@@@@@@+",
3053 "$++++#@@@@@@@@@@@@@+",
3054 "+@@@@@@@@@@@@@@@@@@+",
3055 "+@@@@@@@@@@@@@@@@@@+",
3056 "+@@@@@@@@@@@@@@@@@@+",
3057 "$++++++++++++++++++."
3060 signal_xpm_ok = [
3061 "20 20 10 1",
3062 " c None",
3063 ". c #C6C6C6",
3064 "+ c #CCCCCC",
3065 "@ c #DBDBDB",
3066 "# c #A1A5B2",
3067 "$ c #848DA5",
3068 "% c #D3D3D3",
3069 "& c #4A5B8C",
3070 "* c #677498",
3071 "= c #B0B2B8",
3072 " .+++.",
3073 " +@@@+",
3074 " +@@@+",
3075 " +@@@+",
3076 " +@@@+",
3077 " #$$$$%@@@+",
3078 " $&&&&@@@@+",
3079 " $&&&&@@@@+",
3080 " $&&&&@@@@+",
3081 " $&&&&@@@@+",
3082 " #$$$$*&&&&@@@@+",
3083 " $&&&&&&&&&@@@@+",
3084 " $&&&&&&&&&@@@@+",
3085 " $&&&&&&&&&@@@@+",
3086 " $&&&&&&&&&@@@@+",
3087 "=$$$$*&&&&&&&&&@@@@+",
3088 "$&&&&&&&&&&&&&&@@@@+",
3089 "$&&&&&&&&&&&&&&@@@@+",
3090 "$&&&&&&&&&&&&&&@@@@+",
3091 "=$$$$$$$$$$$$$$++++."
3095 signal_xpm_low = [
3096 "20 20 8 1",
3097 " c None",
3098 ". c #C6C6C6",
3099 "+ c #CCCCCC",
3100 "@ c #DBDBDB",
3101 "# c #D3D3D3",
3102 "$ c #BFB0B5",
3103 "% c #C18799",
3104 "& c #C54F74",
3105 " .+++.",
3106 " +@@@+",
3107 " +@@@+",
3108 " +@@@+",
3109 " +@@@+",
3110 " .++++#@@@+",
3111 " +@@@@@@@@+",
3112 " +@@@@@@@@+",
3113 " +@@@@@@@@+",
3114 " +@@@@@@@@+",
3115 " .++++#@@@@@@@@+",
3116 " +@@@@@@@@@@@@@+",
3117 " +@@@@@@@@@@@@@+",
3118 " +@@@@@@@@@@@@@+",
3119 " +@@@@@@@@@@@@@+",
3120 "$%%%%#@@@@@@@@@@@@@+",
3121 "%&&&&@@@@@@@@@@@@@@+",
3122 "%&&&&@@@@@@@@@@@@@@+",
3123 "%&&&&@@@@@@@@@@@@@@+",
3124 "$%%%%++++++++++++++."
3128 ####################################################################################################
3129 # Make so we can be imported
3130 if __name__ == "__main__":
3131 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
3132 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
3133 else:
3134 import gtk, gobject
3135 gtk.gdk.threads_init()
3136 apQueue = Queue.Queue(100)
3137 commQueue = Queue.Queue(2)
3139 logger = logging.getLogger("wrlog")
3140 logger.setLevel(logging.WARNING)
3141 fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
3142 fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
3143 logger.addHandler(fileLogHandler)
3144 if __debug__:
3145 logger.setLevel(logging.INFO)
3146 consoleLogHandler = logging.StreamHandler()
3147 consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
3148 logger.addHandler(consoleLogHandler)
3150 exit_event = threading.Event()
3151 exit_event.clear()
3152 threading.Thread(None, scanning_thread, None, (confFile, apQueue, commQueue, logger, exit_event)).start()
3153 main_radar_window = radar_window(confFile, apQueue, commQueue, logger, exit_event)
3154 gobject.timeout_add( 500, main_radar_window.update_plist_items )
3155 main_radar_window.main()