Always use console logging
[wifi-radar.git] / wifi-radar
blob42425cd8b2b8bcbb3ac79bdc4df0b101c3676c16
1 #!/usr/bin/python -OO
3 # A wireless profile manager for Linux
5 # Created by:
6 # Ahmad Baitalmal <ahmad@baitalmal.com>
8 # Maintained 2006-2009 by:
9 # Brian Elliott Finley <brian@thefinleys.com>
11 # Maintained by:
12 # Sean Robinson <seankrobinson@gmail.com>
14 # License:
15 # GPL
17 # http://wifi-radar.berlios.de
19 # See CREDITS file for more contributors.
20 # See ChangeLog file for, well, changes.
22 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
23 # turn on console debugging.
25 import ConfigParser
26 import errno
27 import gtk
28 import logging
29 import logging.handlers
30 import os
31 import Queue
32 import re
33 import string
34 import sys
35 import threading
36 from signal import SIGTERM
37 from subprocess import call, Popen, PIPE, STDOUT
38 from time import sleep
39 from types import *
41 WIFI_RADAR_VERSION = "0.0.0"
44 # Where the conf file should live could be different for your distro. Please change
45 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
47 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
49 os.environ['LC_MESSAGES'] = 'C'
52 ####################################################################################################
53 ####################################################################################################
55 # Gets the network interface device
57 #Parameters:
59 # 'device' -- string - The proposed network device to use
61 #Returns:
63 # string -- The actual network device to use
64 def get_network_device(device):
65 #print "get_network_device: %s" % (device, )
66 if device != "auto_detect":
67 return confFile.get_opt('DEFAULT.interface')
68 else:
69 # auto detect network device
70 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
71 # If no devices are found, default to eth1.
72 # call iwconfig command and read output
73 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE, stderr=STDOUT).stdout
74 wireless_devices = [(x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
75 if len(wireless_devices) > 0:
76 return wireless_devices[0]
77 print "No WiFi device found, please set this in the preferences."
78 return ""
80 # Return a blank profile
82 #Parameters:
84 # none
86 #Returns:
88 # dictionary -- An AP profile with defaults set.
89 def get_new_profile():
90 return { 'known': False,
91 'available': False,
92 'encrypted': False,
93 'essid': '',
94 'bssid': '',
95 'roaming': False,
96 'protocol': 'g',
97 'signal': 0,
98 'channel': 'auto',
99 'con_prescript': '',
100 'con_postscript': '',
101 'dis_prescript': '',
102 'dis_postscript': '',
103 'key': '',
104 'mode': 'auto',
105 'security': '',
106 'use_wpa': False,
107 'wpa_driver': '',
108 'use_dhcp': True,
109 'ip': '',
110 'netmask': '',
111 'gateway': '',
112 'domain': '',
113 'dns1': '',
114 'dns2': ''
117 # Combine essid and bssid to make a config file section name
119 #Parameters:
121 # 'essid' -- string - AP ESSID
123 # 'bssid' -- string - AP BSSID
125 #Returns:
127 # string -- the bssid concatenated to a colon, concatenated to the essid
128 def make_section_name( essid, bssid ):
129 return essid + ':' + bssid
131 # Split a config file section name into an essid and a bssid
133 #Parameters:
135 # 'section' -- string - Config file section name
137 #Returns:
139 # list -- the essid and bssid
140 def split_section_name( section ):
141 parts = re.split(':', section)
142 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
144 # Run commands through the shell
146 #Parameters:
148 # 'command' -- tuple - The command and arguments to run.
150 # 'environment' -- dictionary - Environment variables (as keys) and their values.
152 #Returns:
154 # boolean -- True on success, otherwise, False
155 def shellcmd( command, environment = None ):
156 try:
157 env_tmp = os.environ
158 env_tmp.update(environment)
159 command = ' '.join(command)
160 return_code = call(command, shell=True, env=env_tmp)
161 if return_code >= 0:
162 return True
163 else:
164 print >>sys.stderr, "Child was terminated by signal", -return_code
165 except OSError, exception:
166 print >>sys.stderr, "Execution failed:", exception
167 return False
169 # Speak feedback message to user
171 #Parameters:
173 # 'words' -- string - Message to speak to user
175 #Returns:
177 # nothing
178 def say( words ):
179 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
180 words = words.replace( "\"", "\\\"" )
181 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
183 # Scan for a limited time and return AP names and bssid found.
184 # Access points we find will be put on the outgoing Queue, apQueue.
186 #Parameters:
188 # 'confFile' -- ConfigFile - Config file object
190 # 'apQueue' -- Queue - Queue on which to put AP profiles
192 # 'commandQueue' -- Queue - Queue from which to read commands
194 # 'logger' -- Logger - Python's logging facility
196 #Returns:
198 # nothing
199 def scanning_thread(confFile, apQueue, commandQueue, logger, exit_event):
200 logger.info("Begin thread.")
201 # Setup our essid pattern matcher
202 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
203 bssid_pattern = re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
204 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S )
205 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
206 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
207 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
208 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
210 access_points = {}
211 command = "scan"
212 while True:
213 try:
214 command = commandQueue.get_nowait()
215 logger.info("received command: %s" % (command, ))
216 command_read = True
217 except Queue.Empty:
218 command_read = False
219 device = get_network_device(confFile.get_opt('DEFAULT.interface'))
220 if command == "scan":
221 logger.debug("Beginning scan pass")
222 # Some cards need to have the interface up to scan
223 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
224 # call ifconfig command and wait for return
225 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), device, 'up'])
226 # update the signal strengths
227 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), device, 'scan'], stdout=PIPE).stdout.read()
228 # zero out the signal levels for all access points
229 for bssid in access_points:
230 access_points[bssid]['signal'] = 0
231 # split the scan data based on the address line
232 hits = scandata.split(' - ')
233 for hit in hits:
234 # set the defaults for profile template
235 profile = get_new_profile()
236 m = essid_pattern.search( hit )
237 if m:
238 # we found an essid
239 profile['essid'] = m.groups()[1]
240 m = bssid_pattern.search( hit ) # get BSSID from scan
241 if m: profile['bssid'] = m.groups()[1]
242 m = protocol_pattern.search( hit ) # get protocol from scan
243 if m: profile['protocol'] = m.groups()[1]
244 m = mode_pattern.search( hit ) # get mode from scan
245 if m: profile['mode'] = m.groups()[1]
246 m = channel_pattern.search( hit ) # get channel from scan
247 if m: profile['channel'] = m.groups()[1]
248 m = enckey_pattern.search( hit ) # get encryption key from scan
249 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
250 m = signal_pattern.search( hit ) # get signal strength from scan
251 if m: profile['signal'] = m.groups()[1]
252 access_points[ profile['bssid'] ] = profile
253 for bssid in access_points:
254 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
255 # Put all, now or previously, sensed access_points into apQueue
256 try:
257 logger.debug("Scanned profile: %s" % (access_points[ bssid ], ))
258 apQueue.put_nowait( access_points[bssid] )
259 except Queue.Full:
260 pass
261 if command_read:
262 commandQueue.task_done()
263 if exit_event.isSet():
264 logger.info("Exiting.")
265 return
266 if device.find('ath') == 0:
267 sleep( 3 )
268 else:
269 sleep( 1 )
272 # Manage a connection; including reporting connection state,
273 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
274 class ConnectionManager():
275 # Create a new connection manager which can read a config file and send to scanning thread
276 # command Queue. A new manager checks for a pre-existing connection and takes
277 # its AP profile from the ESSID and BSSID to which it is currently attached.
279 #Parameters:
281 # 'confFile' -- ConfigFile - Config file object
283 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
285 # 'logger' -- Logger - Python's logging facility
287 #Returns:
289 # ConnectionManager instance
290 def __init__( self, confFile, commandQueue, logger ):
291 self.confFile = confFile
292 self.commQueue = commandQueue
293 self.logger = logger
294 # is connection running?
295 self.state = False
296 if self.get_current_ip():
297 self.state = True
298 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
300 # Change the interface state: up or down.
302 #Parameters:
304 # 'state' -- string - The state to which to change the interface.
306 #Returns:
308 # nothing
309 def if_change( self, state ):
310 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
311 self.logger.info("changing interface state to %s" % (state, ))
312 # call ifconfig command and wait for return
313 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface')), state])
315 # Connect to the specified AP.
317 #Parameters:
319 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
321 # 'status' -- status implementer - Object which implements status interface.
323 #Returns:
325 # nothing
326 def connect_to_network( self, profile, status ):
327 self.profile = profile
328 if self.profile['bssid'] == '':
329 raise TypeError("Empty AP address")
330 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
331 say( msg )
332 self.logger.info(msg)
333 device = get_network_device(self.confFile.get_opt('DEFAULT.interface'))
334 # ready to dance
335 # Let's run the connection prescript
336 if self.profile['con_prescript'].strip() != '':
337 # got something to execute
338 # run connection prescript through shell and wait for return
339 self.logger.info("executing connection prescript: %s" % (self.profile['con_prescript'], ))
340 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": device})
341 status.show()
342 # Some cards need to have the interface up
343 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
344 self.if_change('up')
345 # Start building iwconfig command line, command
346 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
347 iwconfig_command.append(device)
348 # Setting essid
349 iwconfig_command.append( 'essid' )
350 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
351 # Setting nick
352 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
353 # Setting key
354 iwconfig_command.append( 'key' )
355 if self.profile['key'] == '':
356 iwconfig_command.append( 'off' )
357 else:
358 # Setting this stops association from working, so remove it for now
359 #if self.profile['security'] != '':
360 #iwconfig_command.append(self.profile['security'])
361 iwconfig_command.append( "'" + self.profile['key'] + "'" )
362 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
363 # Setting mode
364 if self.profile['mode'].lower() == 'master' or self.profile['mode'].lower() == 'auto':
365 self.profile['mode'] = 'Managed'
366 iwconfig_command.append( 'mode' )
367 iwconfig_command.append( self.profile['mode'] )
368 # Setting channel
369 if self.profile['channel'] != '':
370 iwconfig_command.append( 'channel' )
371 iwconfig_command.append( self.profile['channel'] )
372 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
373 iwconfig_command.append( 'ap' )
374 iwconfig_command.append( self.profile['bssid'] )
375 # Some cards require a commit
376 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
377 iwconfig_command.append( 'commit' )
378 self.logger.info("iwconfig_command: %s" % (iwconfig_command, ))
379 # call iwconfig command and wait for return
380 if not shellcmd(iwconfig_command): return
381 # Now normal network stuff
382 # Kill off any existing DHCP clients running
383 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
384 self.logger.info("Killing existing DHCP...")
385 try:
386 if self.confFile.get_opt('DHCP.kill_args') != '':
387 # call DHCP client kill command and wait for return
388 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
389 else:
390 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
391 except OSError:
392 print "failed to kill DHCP client"
393 sys.exit()
394 finally:
395 print "Stale pid file. Removing..."
396 os.remove(self.confFile.get_opt('DHCP.pidfile'))
397 # Kill off any existing WPA supplicants running
398 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
399 self.logger.info("Killing existing WPA supplicant...")
400 try:
401 if not self.confFile.get_opt('WPA.kill_command') != '':
402 # call WPA supplicant kill command and wait for return
403 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
404 else:
405 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
406 except OSError:
407 print "failed to kill WPA supplicant"
408 sys.exit()
409 finally:
410 print "Stale pid file. Removing..."
411 os.remove(self.confFile.get_opt('WPA.pidfile'))
412 # Begin WPA supplicant
413 if self.profile['use_wpa'] :
414 self.logger.info("WPA args: %s" % (self.confFile.get_opt('WPA.args'), ))
415 status.update_message("WPA supplicant starting")
416 if sys.modules.has_key("gtk"):
417 while gtk.events_pending():
418 gtk.main_iteration(False)
419 # call WPA supplicant command and do not wait for return
420 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), device])
421 if self.profile['use_dhcp'] :
422 self.logger.debug("Disable iwlist while dhcp in progress...")
423 try:
424 self.commQueue.put("pause")
425 except Queue.Full:
426 pass
427 status.update_message("Acquiring IP Address (DHCP)")
428 if sys.modules.has_key("gtk"):
429 while gtk.events_pending():
430 gtk.main_iteration(False)
431 # call DHCP client command and do not wait for return
432 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
433 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
434 dhcp_command.append(device)
435 self.logger.info("dhcp_command: %s" % (dhcp_command, ))
436 dhcp_proc = Popen(dhcp_command, stdout=None)
437 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
438 tick = 0.25
439 waiting = dhcp_proc.poll()
440 while waiting == None:
441 waiting = dhcp_proc.poll()
442 if timer < 0:
443 os.kill(dhcp_proc.pid, SIGTERM)
444 break
445 if sys.modules.has_key("gtk"):
446 while gtk.events_pending():
447 gtk.main_iteration(False)
448 timer -= tick
449 sleep(tick)
450 # Re-enable iwlist
451 try:
452 self.commQueue.put("scan")
453 except Queue.Full:
454 pass
455 if not self.get_current_ip():
456 status.update_message("Could not get IP address!")
457 if sys.modules.has_key("gtk"):
458 while gtk.events_pending():
459 gtk.main_iteration(False)
460 sleep(1)
461 if self.state:
462 self.disconnect_interface()
463 status.hide()
464 return
465 else:
466 status.update_message("Got IP address. Done.")
467 self.state = True
468 if sys.modules.has_key("gtk"):
469 while gtk.events_pending():
470 gtk.main_iteration(False)
471 sleep(2)
472 else:
473 ifconfig_command= "%s %s down; %s %s %s netmask %s" % ( self.confFile.get_opt('DEFAULT.ifconfig_command'), device, self.confFile.get_opt('DEFAULT.ifconfig_command'), device, self.profile['ip'], self.profile['netmask'] )
474 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
475 resolv_contents = ''
476 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
477 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
478 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
479 if ( resolv_contents != '' ):
480 resolv_file=open('/etc/resolv.conf', 'w')
481 resolv_file.write(s)
482 resolv_file.close
483 if not shellcmd([ifconfig_command]): return
484 if not shellcmd([route_command]): return
485 self.state = True
486 # Let's run the connection postscript
487 con_postscript = self.profile['con_postscript']
488 if self.profile['con_postscript'].strip() != '':
489 self.logger.info("executing connection postscript: %s" % (self.profile['con_postscript'], ))
490 shellcmd([self.profile['con_postscript']],
491 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
492 "WIFIRADAR_ESSID": self.get_current_essid() or '',
493 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
494 "WIFIRADAR_IF": device or ''
497 status.hide()
499 # Disconnect from the AP with which a connection has been established/attempted.
501 #Parameters:
503 # nothing
505 #Returns:
507 # nothing
508 def disconnect_interface( self ):
509 msg = "Disconnecting"
510 say( msg )
511 self.logger.info(msg)
512 device = get_network_device(self.confFile.get_opt('DEFAULT.interface'))
513 # Pause scanning while manipulating card
514 try:
515 self.commQueue.put("pause")
516 self.commQueue.join()
517 except Queue.Full:
518 pass
519 if self.state:
520 self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), self.get_current_bssid()))
521 if not self.profile:
522 self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), ''))
523 if not self.profile:
524 raise KeyError
525 # Let's run the disconnection prescript
526 if self.profile['dis_prescript'].strip() != '':
527 self.logger.info("executing disconnection prescript: %s" % (self.profile['dis_prescript'], ))
528 shellcmd([self.profile['dis_prescript']],
529 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
530 "WIFIRADAR_ESSID": self.get_current_essid() or '',
531 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
532 "WIFIRADAR_IF": device or ''
535 self.logger.info("Kill off any existing DHCP clients running...")
536 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
537 self.logger.info("Killing existing DHCP...")
538 try:
539 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
540 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
541 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
542 dhcp_command.append(device)
543 self.logger.info("DHCP command: %s" % (dhcp_command, ))
544 # call DHCP client command and wait for return
545 if not shellcmd(dhcp_command): return
546 else:
547 self.logger.info("Killing DHCP manually...")
548 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
549 except OSError:
550 print "failed to kill DHCP client"
551 self.logger.info("Kill off any existing WPA supplicants running...")
552 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
553 self.logger.info("Killing existing WPA supplicant...")
554 try:
555 if not self.confFile.get_opt('WPA.kill_command') != '':
556 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
557 if not shellcmd(wpa_command): return
558 else:
559 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
560 except OSError:
561 print "failed to kill WPA supplicant"
562 self.logger.info("Let's clear out the wireless stuff")
563 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), device, 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
564 self.logger.info("Now take the interface down")
565 self.logger.info("Since it may be brought back up by the next scan, lets unset its IP")
566 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), device, '0.0.0.0'])
567 # taking down the interface too quickly can crash my system, so pause a moment
568 sleep(1)
569 self.if_change('down')
570 # Let's run the disconnection postscript
571 if self.profile['dis_postscript'].strip() != '':
572 self.logger.info("executing disconnection postscript: %s" % (self.profile['dis_postscript'], ))
573 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": device})
574 self.state = False
575 self.logger.info("Disconnect complete.")
576 # Begin scanning again
577 try:
578 self.commQueue.put("scan")
579 except Queue.Full:
580 pass
582 # Returns the current IP, if any, by calling ifconfig.
584 #Parameters:
586 # nothing
588 #Returns:
590 # string or None -- the IP address or None (if no there is no current connection)
591 def get_current_ip( self ):
592 ifconfig_command = [confFile.get_opt('DEFAULT.ifconfig_command'), get_network_device(confFile.get_opt('DEFAULT.interface'))]
593 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
594 # Be careful to the language (inet adr: in French for example)
596 # Hi Brian
598 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
599 # There the string in ifconfig is inet Adresse for the IP which isn't
600 # found by the current get_current_ip function in wifi-radar. I changed
601 # the according line (#289; gentoo, v1.9.6-r1) to
602 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
603 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
605 # I'd be happy if you could incorporate this small change because as now
606 # I've got to change the file every time it is updated.
608 # Best wishes
610 # Simon
611 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
612 line = ifconfig_info.read()
613 if ip_re.search( line ):
614 return ip_re.search( line ).group(1)
615 return None
617 # Returns the current ESSID, if any, by calling iwconfig.
619 #Parameters:
621 # nothing
623 #Returns:
625 # string or None -- the ESSID or None (if no there is no current association)
626 def get_current_essid( self ):
627 """Returns the current ESSID if any by calling iwconfig"""
628 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface'))], stdout=PIPE, stderr=STDOUT).stdout
629 # Be careful to the language (inet adr: in French for example)
630 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
631 line = iwconfig_info.read()
632 if essid_re.search( line ):
633 return essid_re.search( line ).group(2)
634 return None
636 # Returns the current BSSID, if any, by calling iwconfig.
638 #Parameters:
640 # nothing
642 #Returns:
644 # string or None -- the BSSID or None (if no there is no current association)
645 def get_current_bssid( self ):
646 """Returns the current BSSID if any by calling iwconfig"""
647 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), get_network_device(self.confFile.get_opt('DEFAULT.interface'))], stdout=PIPE, stderr=STDOUT).stdout
648 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
649 line = iwconfig_info.read()
650 if bssid_re.search( line ):
651 return bssid_re.search( line ).group(2)
652 return None
656 # The main user interface window for WiFi Radar. This class also is the control
657 # center for most of the rest of the operations.
658 class radar_window:
659 # Create a new radar_window.
661 #Parameters:
663 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
665 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
667 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
669 # 'logger' -- Logger - Python's logging facility
671 #Returns:
673 # radar_window instance
674 def __init__(self, confFile, apQueue, commQueue, logger, exit_event):
675 global signal_xpm_none
676 global signal_xpm_low
677 global signal_xpm_barely
678 global signal_xpm_ok
679 global signal_xpm_best
680 global known_profile_icon
681 global unknown_profile_icon
682 global wifi_radar_icon
684 self.confFile = confFile
685 self.apQueue = apQueue
686 self.commandQueue = commQueue
687 self.logger = logger
688 self.access_points = {}
689 self.exit_event = exit_event
690 self.connection = None
692 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
693 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
694 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
695 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
696 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
697 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
698 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
699 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
700 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
701 self.window.set_icon( icon )
702 self.window.set_border_width( 10 )
703 self.window.set_size_request( 550, 300 )
704 self.window.set_title( "WiFi Radar" )
705 self.window.connect( 'delete_event', self.delete_event )
706 self.window.connect( 'destroy', self.destroy )
707 # let's create all our widgets
708 self.current_network = gtk.Label()
709 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
710 self.current_network.show()
711 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
712 self.close_button.show()
713 self.close_button.connect( 'clicked', self.delete_event, None )
714 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
715 self.about_button.show()
716 self.about_button.connect( 'clicked', self.show_about_info, None )
717 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
718 self.preferences_button.show()
719 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
720 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
721 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
722 self.plist = gtk.TreeView( self.pstore )
723 # The icons column, known and encryption
724 self.pix_cell = gtk.CellRendererPixbuf()
725 self.wep_cell = gtk.CellRendererPixbuf()
726 self.icons_cell = gtk.CellRendererText()
727 self.icons_col = gtk.TreeViewColumn()
728 self.icons_col.pack_start( self.pix_cell, False )
729 self.icons_col.pack_start( self.wep_cell, False )
730 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
731 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
732 self.plist.append_column( self.icons_col )
733 # The AP column
734 self.ap_cell = gtk.CellRendererText()
735 self.ap_col = gtk.TreeViewColumn( "Access Point" )
736 self.ap_col.pack_start( self.ap_cell, True )
737 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
738 self.plist.append_column( self.ap_col )
739 # The signal column
740 self.sig_cell = gtk.CellRendererPixbuf()
741 self.signal_col = gtk.TreeViewColumn( "Signal" )
742 self.signal_col.pack_start( self.sig_cell, True )
743 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
744 self.plist.append_column( self.signal_col )
745 # The mode column
746 self.mode_cell = gtk.CellRendererText()
747 self.mode_col = gtk.TreeViewColumn( "Mode" )
748 self.mode_col.pack_start( self.mode_cell, True )
749 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
750 self.plist.append_column( self.mode_col )
751 # The protocol column
752 self.prot_cell = gtk.CellRendererText()
753 self.protocol_col = gtk.TreeViewColumn( "802.11" )
754 self.protocol_col.pack_start( self.prot_cell, True )
755 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
756 self.plist.append_column( self.protocol_col )
757 # The channel column
758 self.channel_cell = gtk.CellRendererText()
759 self.channel_col = gtk.TreeViewColumn( "Channel" )
760 self.channel_col.pack_start( self.channel_cell, True )
761 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
762 self.plist.append_column( self.channel_col )
763 # DnD Ordering
764 self.plist.set_reorderable( True )
765 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
766 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
767 # enable/disable buttons based on the selected network
768 self.selected_network = self.plist.get_selection()
769 self.selected_network.connect( 'changed', self.on_network_selection, None )
770 # the list scroll bar
771 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
772 sb.show()
773 self.plist.show()
774 # Add New button
775 self.new_button = gtk.Button( "_New" )
776 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
777 self.new_button.show()
778 # Add Configure button
779 self.edit_button = gtk.Button( "C_onfigure" )
780 self.edit_button.connect( 'clicked', self.edit_profile, None )
781 self.edit_button.show()
782 self.edit_button.set_sensitive(False)
783 # Add Delete button
784 self.delete_button = gtk.Button( "_Delete" )
785 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
786 self.delete_button.show()
787 self.delete_button.set_sensitive(False)
788 # Add Connect button
789 self.connect_button = gtk.Button( "Co_nnect" )
790 self.connect_button.connect( 'clicked', self.connect_profile, None )
791 # Add Disconnect button
792 self.disconnect_button = gtk.Button( "D_isconnect" )
793 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
794 # lets add our widgets
795 rows = gtk.VBox( False, 3 )
796 net_list = gtk.HBox( False, 0 )
797 listcols = gtk.HBox( False, 0 )
798 prows = gtk.VBox( False, 0 )
799 # lets start packing
800 # the network list
801 net_list.pack_start( self.plist, True, True, 0 )
802 net_list.pack_start( sb, False, False, 0 )
803 # the rows level
804 rows.pack_start( net_list , True, True, 0 )
805 rows.pack_start( self.current_network, False, True, 0 )
806 # the list columns
807 listcols.pack_start( rows, True, True, 0 )
808 listcols.pack_start( prows, False, False, 5 )
809 # the list buttons
810 prows.pack_start( self.new_button, False, False, 2 )
811 prows.pack_start( self.edit_button, False, False, 2 )
812 prows.pack_start( self.delete_button, False, False, 2 )
813 prows.pack_end( self.connect_button, False, False, 2 )
814 prows.pack_end( self.disconnect_button, False, False, 2 )
816 self.window.action_area.pack_start( self.about_button )
817 self.window.action_area.pack_start( self.preferences_button )
818 self.window.action_area.pack_start( self.close_button )
820 rows.show()
821 prows.show()
822 listcols.show()
823 self.window.vbox.add( listcols )
824 self.window.vbox.set_spacing( 3 )
825 self.window.show_all()
827 # Now, immediately hide these two. The proper one will be
828 # displayed later, based on interface state. -BEF-
829 self.disconnect_button.hide()
830 self.connect_button.hide()
831 self.connect_button.set_sensitive(False)
833 # set up connection manager for later use
834 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
835 # set up status window for later use
836 self.status_window = StatusWindow( self )
837 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
839 # Add our known profiles in order
840 for profile_name in self.confFile.auto_profile_order:
841 profile_name = profile_name.strip()
842 self.access_points[profile_name] = self.confFile.get_profile(profile_name)
843 wep = None
844 if self.access_points[profile_name]['encrypted']:
845 wep = gtk.STOCK_DIALOG_AUTHENTICATION
846 if self.access_points[profile_name]['roaming']:
847 ap_name = self.access_points[profile_name]['essid'] + "\n" + ' Multiple APs'
848 else:
849 ap_name = self.access_points[profile_name]['essid'] + "\n" + self.access_points[profile_name]['bssid']
850 self.pstore.append([ap_name, self.known_profile_icon, self.access_points[profile_name]['known'], self.access_points[profile_name]['available'], wep, self.signal_none_pb, self.access_points[profile_name]['mode'], self.access_points[profile_name]['protocol'], self.access_points[profile_name]['channel'] ] )
851 # This is the first run (or, at least, no config file was present), so pop up the preferences window
852 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
853 self.confFile.remove_option('DEFAULT', 'new_file')
854 self.edit_preferences(self.preferences_button)
856 # Begin running radar_window in Gtk event loop.
858 #Parameters:
860 # nothing
862 #Returns:
864 # nothing
865 def main( self ):
866 gtk.main()
868 # Quit application.
870 #Parameters:
872 # 'widget' -- gtk.Widget - The widget sending the event.
874 #Returns:
876 # nothing
877 def destroy( self, widget = None):
878 if self.status_window:
879 self.status_window.destroy()
880 gtk.main_quit()
882 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
884 #Parameters:
886 # 'widget' -- gtk.Widget - The widget sending the event.
888 # 'data' -- tuple - list of arbitrary arguments (not used)
890 #Returns:
892 # boolean -- always return False (i.e. do not propigate the signal which called)
893 def delete_event( self, widget, data = None ):
894 # Let other threads know it is time to exit
895 self.exit_event.set()
896 # Wait for all other threads to exit before continuing
897 while threading.activeCount() > 1:
898 sleep(0.25)
899 self.destroy()
900 return False
902 # Update the current ip and essid shown to user.
904 #Parameters:
906 # nothing
908 #Returns:
910 # nothing
911 def update_network_info(self):
912 if self.connection and self.connection.state:
913 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()))
914 else:
915 self.current_network.set_text("Not Connected.")
917 # Set the state of connect/disconnect buttons based on whether we have a connection.
919 #Parameters:
921 # nothing
923 #Returns:
925 # nothing
926 def update_connect_buttons(self):
927 if self.connection and self.connection.state:
928 self.connect_button.hide()
929 self.disconnect_button.show()
930 else:
931 self.disconnect_button.hide()
932 self.connect_button.show()
934 # Updates the on-screen profiles list.
936 #Parameters:
938 # 'ap' -- dictionary -- The AP found by scanning_thread.
940 #Returns:
942 # nothing
943 def update_plist_items(self, ap):
944 # Check for roaming profile.
945 ap_name = make_section_name(ap['essid'], '')
946 profile = self.confFile.get_profile(ap_name)
947 prow_iter = self.get_row_by_ap(ap['essid'], ' Multiple APs')
948 ap_display = ap['essid'] + "\n" + ' Multiple APs'
949 if not profile:
950 # Check for normal profile.
951 ap_name = make_section_name(ap['essid'], ap['bssid'])
952 profile = self.confFile.get_profile(ap_name)
953 prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
954 ap_display = ap['essid'] + "\n" + ap['bssid']
955 if not profile:
956 # No profile, so make a new one.
957 profile = get_new_profile()
958 wep = None
959 if prow_iter != None:
960 # the AP is in the list of APs on the screen
961 # Set the 'known' values; False is default, overridden to True by self.access_points
962 ap['known'] = self.access_points[ap_name]['known']
963 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
964 self.pstore.set_value(prow_iter, 2, ap['known'])
965 self.pstore.set_value(prow_iter, 3, ap['available'])
966 if ap['encrypted']:
967 wep = gtk.STOCK_DIALOG_AUTHENTICATION
968 self.pstore.set_value(prow_iter, 4, wep)
969 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(self.access_points[ap_name]['signal']))
970 self.pstore.set_value(prow_iter, 6, ap['mode'])
971 self.pstore.set_value(prow_iter, 7, ap['protocol'])
972 self.pstore.set_value(prow_iter, 8, self.access_points[ap_name]['channel'])
973 else:
974 # the AP is not in the list of APs on the screen
975 self.pstore.append([ap_display, self.pixbuf_from_known(ap['known']), ap['known'], ap['available'], wep, self.pixbuf_from_signal(ap['signal']), ap['mode'], ap['protocol'], ap['channel']])
976 #print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']
978 # Updates the record-keeping profiles list.
980 #Parameters:
982 # 'ap' -- dictionary -- The AP found by scanning_thread.
984 #Returns:
986 # nothing
987 def update_ap_list(self, ap):
988 # Check for roaming profile.
989 ap_name = make_section_name(ap['essid'], '')
990 profile = self.confFile.get_profile(ap_name)
991 if not profile:
992 # Check for normal profile.
993 ap_name = make_section_name(ap['essid'], ap['bssid'])
994 profile = self.confFile.get_profile(ap_name)
995 if not profile:
996 # No profile, so make a new one.
997 profile = get_new_profile()
998 if self.access_points.has_key(ap_name):
999 # This AP has been configured and should be updated
1000 self.access_points[ap_name]['known'] = profile['known']
1001 self.access_points[ap_name]['available'] = ap['available']
1002 self.access_points[ap_name]['encrypted'] = ap['encrypted']
1003 self.access_points[ap_name]['mode'] = ap['mode']
1004 self.access_points[ap_name]['protocol'] = ap['protocol']
1005 if self.access_points[ap_name]['roaming']:
1006 # Roaming
1007 if self.access_points[ap_name]['bssid'] == ap['bssid']:
1008 # Same AP for this roaming profile
1009 self.access_points[ap_name]['signal'] = ap['signal']
1010 else:
1011 # Different AP
1012 if int(self.access_points[ap_name]['signal']) < int(ap['signal']):
1013 # Stronger signal with this AP, so promote it to preferred
1014 self.access_points[ap_name]['signal'] = ap['signal']
1015 self.access_points[ap_name]['channel'] = ap['channel']
1016 self.access_points[ap_name]['bssid'] = ap['bssid']
1017 else:
1018 # Not roaming
1019 self.access_points[ap_name]['signal'] = ap['signal']
1020 self.access_points[ap_name]['channel'] = ap['channel']
1021 else:
1022 # Not seen before, begin tracking it.
1023 self.access_points[ap_name] = profile
1025 # Updates the main user interface.
1027 #Parameters:
1029 # nothing
1031 #Returns:
1033 # boolean -- always return True
1034 def update_window(self):
1035 # Indicate to PyGtk that only one Gtk thread should run here
1036 gtk.gdk.threads_enter()
1037 self.update_network_info()
1038 self.update_connect_buttons()
1039 while True:
1040 # Get APs scanned by iwlist
1041 try:
1042 ap = self.apQueue.get_nowait()
1043 except Queue.Empty:
1044 break
1045 else:
1046 self.update_ap_list(ap)
1047 self.update_plist_items(ap)
1048 # Allow other Gtk threads to run
1049 gtk.gdk.threads_leave()
1050 return True
1052 # Return the proper icon for a value of known.
1054 #Parameters:
1056 # 'known' -- boolean - Whether the AP is known (i.e. configured)
1058 #Returns:
1060 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
1061 def pixbuf_from_known( self, known ):
1062 """ return the proper icon for value of known """
1063 if known:
1064 return self.known_profile_icon
1065 else:
1066 return self.unknown_profile_icon
1068 # Return an icon indicating the signal level.
1070 #Parameters:
1072 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
1074 #Returns:
1076 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
1077 def pixbuf_from_signal( self, signal ):
1078 signal = int( signal )
1079 # shift signal up by 80 to convert dBm scale to arbitrary scale
1080 if signal < 0: signal = signal + 80
1081 #print "signal level:", signal
1082 if signal < 3:
1083 return self.signal_none_pb
1084 elif signal < 12:
1085 return self.signal_low_pb
1086 elif signal < 20:
1087 return self.signal_barely_pb
1088 elif signal < 35:
1089 return self.signal_ok_pb
1090 elif signal >= 35:
1091 return self.signal_best_pb
1092 else:
1093 return None
1095 # Return row which holds specified ESSID and BSSID.
1097 #Parameters:
1099 # 'essid' -- string - ESSID to match
1101 # 'bssid' -- string - BSSID to match
1103 #Returns:
1105 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1106 def get_row_by_ap( self, essid, bssid ):
1107 if bssid == "roaming":
1108 for row in self.pstore:
1109 if (row[0][:row[0].find("\n")] == essid):
1110 #print "roaming match:", row.iter, essid, bssid
1111 return row.iter
1112 else:
1113 for row in self.pstore:
1114 if (row[0] == essid + "\n" + bssid):
1115 #print "normal match:", row.iter, essid, bssid
1116 return row.iter
1117 return None
1119 # Enable/disable buttons based on the selected network.
1121 #Parameters:
1123 # 'widget' -- gtk.Widget - The widget sending the event.
1125 # 'data' -- tuple - list of arbitrary arguments (not used)
1127 #Returns:
1129 # nothing
1130 def on_network_selection( self, widget, data = None ):
1131 ( store, selected_iter ) = self.selected_network.get_selected()
1132 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1133 # if no networks are selected, disable all buttons except New
1134 # (this occurs after a drag-and-drop)
1135 if selected_iter == None:
1136 self.edit_button.set_sensitive(False)
1137 self.delete_button.set_sensitive(False)
1138 self.connect_button.set_sensitive(False)
1139 return
1140 # enable/disable buttons
1141 self.connect_button.set_sensitive(True)
1142 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1143 self.edit_button.set_sensitive(True)
1144 self.delete_button.set_sensitive(True)
1145 else:
1146 self.edit_button.set_sensitive(True)
1147 self.delete_button.set_sensitive(False)
1149 # Init and run the about dialog
1151 #Parameters:
1153 # 'widget' -- gtk.Widget - The widget sending the event.
1155 # 'data' -- tuple - list of arbitrary arguments (not used)
1157 #Returns:
1159 # nothing
1160 def show_about_info( self, widget, data=None ):
1161 about = about_dialog()
1162 about.run()
1163 about.destroy()
1165 # Init and run the preferences dialog
1167 #Parameters:
1169 # 'widget' -- gtk.Widget - The widget sending the event.
1171 # 'data' -- tuple - list of arbitrary arguments (not used)
1173 #Returns:
1175 # nothing
1176 def edit_preferences( self, widget, data=None ):
1177 # get raw strings from config file
1178 self.confFile.raw = True
1179 prefs = preferences_dialog( self, self.confFile )
1180 response = prefs.run()
1181 if response == int(gtk.RESPONSE_ACCEPT):
1182 prefs.save()
1183 prefs.destroy()
1184 # get cooked strings from config file
1185 self.confFile.raw = False
1187 # Respond to a request to create a new AP profile
1189 #Parameters:
1191 # 'widget' -- gtk.Widget - The widget sending the event.
1193 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1195 # 'data' -- tuple - list of arbitrary arguments (not used)
1197 #Returns:
1199 # boolean -- True if a profile was created and False if profile creation was canceled.
1200 def create_new_profile( self, widget, profile, data=None ):
1201 profile_editor = profile_dialog( self, profile )
1202 try:
1203 profile = profile_editor.run()
1204 except ValueError:
1205 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1206 del error_dlg
1207 return False
1208 finally:
1209 profile_editor.destroy()
1210 if profile:
1211 (store, selected_iter) = self.plist.get_selection().get_selected()
1212 if selected_iter is not None:
1213 store.remove(selected_iter)
1214 if profile['roaming']:
1215 apname = make_section_name(profile['essid'], '')
1216 else:
1217 apname = make_section_name(profile['essid'], profile['bssid'])
1218 # Check that the ap does not exist already
1219 if apname in self.confFile.profiles():
1220 error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
1221 del error_dlg
1222 # try again
1223 self.access_points[ apname ] = profile
1224 self.confFile.set_section( apname, profile )
1225 # if it is not in the auto_profile_order add it
1226 if apname not in self.confFile.auto_profile_order:
1227 self.confFile.auto_profile_order.insert(0, apname)
1228 # add to the store
1229 wep = None
1230 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1231 try:
1232 self.confFile.write()
1233 except IOError, (error_number, error_str):
1234 if error_number == errno.ENOENT:
1235 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1236 del error_dlg
1237 else:
1238 raise IOError(error_number, error_str)
1239 # Add AP to the list displayed to user
1240 if profile['roaming']:
1241 ap_name = profile['essid'] + "\n" + ' Multiple APs'
1242 while True:
1243 prow_iter = self.get_row_by_ap(profile['essid'], 'roaming')
1244 if prow_iter:
1245 self.pstore.remove(prow_iter)
1246 else:
1247 break
1248 else:
1249 ap_name = profile['essid'] + "\n" + profile['bssid']
1250 self.pstore.prepend([ap_name, self.pixbuf_from_known(profile['known']), profile['known'], profile['available'], wep, self.pixbuf_from_signal(profile['signal']), profile['mode'], profile['protocol'], profile['channel']])
1251 return True
1252 else:
1253 # Did not create new profile
1254 return False
1256 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1257 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1259 #Parameters:
1261 # 'widget' -- gtk.Widget - The widget sending the event.
1263 # 'data' -- tuple - list of arbitrary arguments (not used)
1265 #Returns:
1267 # nothing
1268 def edit_profile(self, widget, data=None):
1269 (store, selected_iter) = self.plist.get_selection().get_selected()
1270 if not selected_iter:
1271 # No AP is selected
1272 return
1273 (essid, bssid) = str(self.pstore.get_value(selected_iter, 0)).split("\n")
1274 if bssid == ' Multiple APs':
1275 # AP list says this is a roaming profile
1276 apname = make_section_name(essid, '')
1277 else:
1278 # AP list says this is NOT a roaming profile
1279 apname = make_section_name(essid, bssid)
1280 profile = self.confFile.get_profile(apname)
1281 if profile:
1282 # A profile was found in the config file
1283 profile['bssid'] = self.access_points[apname]['bssid']
1284 profile_editor = profile_dialog(self, profile)
1285 try:
1286 # try editing the profile
1287 edited_profile = profile_editor.run()
1288 except ValueError:
1289 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1290 del error_dlg
1291 return False
1292 finally:
1293 # Always remove profile editor window from screen
1294 profile_editor.destroy()
1295 if edited_profile:
1296 # A profile was returned by the editor
1297 old_index = None
1298 row = None
1299 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
1300 # ESSID, BSSID, or roaming was changed in profile editor
1301 try:
1302 self.commandQueue.put('pause')
1303 self.commandQueue.join()
1304 except Queue.Full:
1305 pass
1306 if profile['roaming']:
1307 # The old profile was a roaming profile
1308 old_ap = make_section_name(profile['essid'], '')
1309 else:
1310 # The old profile was NOT a roaming profile
1311 old_ap = make_section_name(profile['essid'], profile['bssid'])
1312 # Find where old profile was in auto order
1313 old_index = self.confFile.auto_profile_order.index(old_ap)
1314 # Remove old profile and get its place in AP list
1315 row = self.delete_profile(selected_iter, old_ap)
1316 self.confFile.remove_section(old_ap)
1317 try:
1318 # Add AP to the list displayed to user
1319 self.apQueue.put_nowait(edited_profile)
1320 self.commandQueue.put('scan')
1321 except Queue.Full:
1322 pass
1323 if edited_profile['roaming']:
1324 # New profile is a roaming profile
1325 apname = make_section_name(edited_profile['essid'], '')
1326 ap_display = edited_profile['essid'] + "\n" + ' Multiple APs'
1327 # Remove all other profiles that match the new profile ESSID
1328 while True:
1329 prow_iter = self.get_row_by_ap(edited_profile['essid'], 'roaming')
1330 if prow_iter:
1331 self.pstore.remove(prow_iter)
1332 else:
1333 break
1334 else:
1335 # New profile is NOT a roaming profile
1336 apname = make_section_name(edited_profile['essid'], edited_profile['bssid'])
1337 ap_display = edited_profile['essid'] + "\n" + edited_profile['bssid']
1338 # Insert the new profile in the same position as the one being replaced
1339 if old_index != None:
1340 # Old profile was in auto order list
1341 self.confFile.auto_profile_order.insert(old_index, apname)
1342 if row != None:
1343 self.pstore.insert_before(row, [ap_display, None, None, None, None, None, None, None, None])
1344 self.access_points[apname] = edited_profile
1345 self.confFile.set_section(apname, edited_profile)
1346 try:
1347 # Save updated profile to config file
1348 self.confFile.write()
1349 except IOError, (error_number, error_str):
1350 if error_number == errno.ENOENT:
1351 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1352 del error_dlg
1353 else:
1354 raise IOError(error_number, error_str)
1355 else:
1356 # The AP does not already have a profile
1357 profile = get_new_profile()
1358 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1359 self.create_new_profile( widget, profile, data )
1361 # Delete an AP profile (i.e. make profile unknown)
1363 #Parameters:
1365 # 'selected_iter' -- gtk.TreeIter - The selected row.
1367 # 'apname' -- string - The configuration file section to remove
1369 #Returns:
1371 # gtk.TreeIter -- the iter for the row removed from the gtk.ListStore
1372 def delete_profile(self, selected_iter, apname):
1373 # Remove it
1374 del self.access_points[apname]
1375 self.confFile.remove_section(apname)
1376 self.logger.info(apname)
1377 if apname in self.confFile.auto_profile_order:
1378 self.confFile.auto_profile_order.remove(apname)
1379 self.pstore.remove(selected_iter)
1380 # Let's save our current state
1381 self.update_auto_profile_order()
1382 try:
1383 self.confFile.write()
1384 except IOError, (error_number, error_str):
1385 if error_number == errno.ENOENT:
1386 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1387 del error_dlg
1388 else:
1389 raise IOError(error_number, error_str)
1390 return selected_iter
1392 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1393 # Check with user first.
1395 #Parameters:
1397 # 'widget' -- gtk.Widget - The widget sending the event.
1399 # 'data' -- tuple - list of arbitrary arguments (not used)
1401 #Returns:
1403 # nothing
1404 def delete_profile_with_check(self, widget, data=None):
1405 (store, selected_iter) = self.plist.get_selection().get_selected()
1406 if not selected_iter: return
1407 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
1408 if bssid == ' Multiple APs':
1409 apname = make_section_name(essid, '')
1410 else:
1411 apname = make_section_name(essid, bssid)
1412 profile = self.confFile.get_profile(apname)
1413 if profile['roaming']:
1414 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s profile?" % (essid, ))
1415 else:
1416 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid))
1417 known = store.get_value( selected_iter, 1 )
1418 if not known: return
1419 res = dlg.run()
1420 dlg.destroy()
1421 del dlg
1422 if res == gtk.RESPONSE_NO:
1423 return
1424 self.delete_profile(selected_iter, apname)
1426 # Respond to a request to connect to an AP.
1428 #Parameters:
1430 # 'widget' -- gtk.Widget - The widget sending the event.
1432 # 'profile' -- dictionary - The AP profile to which to connect.
1434 # 'data' -- tuple - list of arbitrary arguments (not used)
1436 #Returns:
1438 # nothing
1439 def connect_profile( self, widget, profile, data=None ):
1440 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1441 if not selected_iter: return
1442 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1443 known = store.get_value( selected_iter, 2 )
1444 if not known:
1445 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "This network does not have a profile configured.\n\nWould you like to create one now?")
1446 res = dlg.run()
1447 dlg.destroy()
1448 del dlg
1449 if res == gtk.RESPONSE_NO:
1450 return
1451 profile = get_new_profile()
1452 profile['essid'] = essid
1453 profile['bssid'] = bssid
1454 if not self.create_new_profile( widget, profile, data ):
1455 return
1456 else:
1457 # Check for roaming profile.
1458 ap_name = make_section_name(essid, '')
1459 profile = self.confFile.get_profile(ap_name)
1460 if not profile:
1461 # Check for normal profile.
1462 ap_name = make_section_name(essid, bssid)
1463 profile = self.confFile.get_profile(ap_name)
1464 if not profile:
1465 # No configured profile
1466 return
1467 profile['bssid'] = self.access_points[ap_name]['bssid']
1468 profile['channel'] = self.access_points[ap_name]['channel']
1469 self.connection.connect_to_network(profile, self.status_window)
1471 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1473 #Parameters:
1475 # 'widget' -- gtk.Widget - The widget sending the event.
1477 # 'data' -- tuple - list of arbitrary arguments (not used)
1479 #Returns:
1481 # nothing
1482 def disconnect_profile( self, widget, data=None ):
1483 if data == "cancel":
1484 self.status_window.update_message("Canceling connection...")
1485 if sys.modules.has_key("gtk"):
1486 while gtk.events_pending():
1487 gtk.main_iteration(False)
1488 sleep(1)
1489 self.connection.disconnect_interface()
1491 # Update the config file auto profile order from the on-screen order
1493 #Parameters:
1495 # 'widget' -- gtk.Widget - The widget sending the event.
1497 # 'data' -- tuple - list of arbitrary arguments (not used)
1499 # 'data2' -- tuple - list of arbitrary arguments (not used)
1501 #Returns:
1503 # nothing
1504 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1505 # recreate the auto_profile_order
1506 auto_profile_order = []
1507 piter = self.pstore.get_iter_first()
1508 while piter:
1509 # only if it's known
1510 if self.pstore.get_value(piter, 2) == True:
1511 (essid, bssid) = self.pstore.get_value(piter, 0).split("\n")
1512 if bssid == ' Multiple APs':
1513 apname = make_section_name(essid, '')
1514 else:
1515 apname = make_section_name(essid, bssid)
1516 auto_profile_order.append(apname)
1517 piter = self.pstore.iter_next(piter)
1518 self.confFile.auto_profile_order = auto_profile_order
1519 try:
1520 self.confFile.write()
1521 except IOError, (error_number, error_str):
1522 if error_number == errno.ENOENT:
1523 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1524 del error_dlg
1525 else:
1526 raise IOError(error_number, error_str)
1529 # Button to allow user to choose a file and put value into specified gtk.Entry
1530 class file_browse_button(gtk.Button):
1531 # Create a button to simulate a File/Open
1533 #Parameters:
1535 # 'parent' -- gtk.Object -- Usually, the calling window.
1537 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1539 #Returns:
1541 # file_browse_button instance
1542 def __init__( self, parent, entry ):
1543 self.parent_window = parent
1544 self.entry = entry
1545 gtk.Button.__init__(self, "Browse", None)
1546 #self.
1547 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)
1548 self.connect("clicked", self.browse_files)
1550 # Show filechooser dialog and get user selection
1552 #Parameters:
1554 # 'widget' -- gtk.Widget -- The widget sending the event.
1556 #Returns:
1558 # nothing
1560 #NOTES:
1562 # updates entry value
1564 def browse_files( self, widget ):
1565 self.browser_dialog.set_filename(self.entry.get_text())
1566 self.browser_dialog.run()
1567 self.entry.set_text(self.browser_dialog.get_filename())
1568 self.browser_dialog.destroy()
1571 # Simple dialog to report an error to the user.
1572 class ErrorDialog:
1573 # Create a new ErrorDialog.
1575 #Parameters:
1577 # 'parent' -- gtk.Object - Usually, the calling window.
1579 # 'message' -- string - The message to display to the user.
1581 #Returns:
1583 # ErrorDialog instance
1584 def __init__( self, parent, message ):
1585 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1586 dialog.run()
1587 dialog.destroy()
1588 del dialog
1591 # The preferences dialog. Edits non-profile sections of the config file.
1592 class preferences_dialog:
1593 # Create a new preferences_dialog.
1595 #Parameters:
1597 # 'parent' -- gtk.Object - Usually, the calling window.
1599 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1601 #Returns:
1603 # preferences_dialog instance
1604 def __init__( self, parent, confFile ):
1605 global wifi_radar_icon
1606 self.parent = parent
1607 self.confFile = confFile
1608 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1609 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1610 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1611 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1612 self.dialog.set_icon( icon )
1613 self.dialog.set_resizable( True )
1614 self.dialog.set_transient_for( self.parent.window )
1615 self.tooltips = gtk.Tooltips()
1617 # set up preferences widgets
1619 # build everything in a tabbed notebook
1620 self.prefs_notebook = gtk.Notebook()
1622 ### General tab
1623 self.general_page = gtk.VBox()
1624 # auto detect wireless device
1625 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1627 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1628 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1629 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1630 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1632 # network interface selecter
1633 self.w_interface = gtk.combo_box_entry_new_text()
1634 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE, stderr=STDOUT).stdout
1635 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1636 for device in wireless_devices:
1637 if device != self.confFile.get_opt('DEFAULT.interface'):
1638 self.w_interface.append_text(device)
1639 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1640 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1641 self.w_interface.set_active(0)
1642 self.w_interface_label = gtk.Label("Wireless device")
1643 self.w_hbox1 = gtk.HBox(False, 0)
1644 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1645 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1646 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1647 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1649 # scan timeout (spin button of integers from 1 to 100)
1650 #self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1651 #self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1652 #self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1653 #self.w_scan_timeout.set_numeric(True)
1654 #self.w_scan_timeout.set_snap_to_ticks(True)
1655 #self.w_scan_timeout.set_wrap(False)
1656 #self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1657 #self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1658 #self.w_hbox2 = gtk.HBox(False, 0)
1659 #self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1660 #self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1661 #self.general_page.pack_start(self.w_hbox2, False, False, 5)
1663 # speak up
1664 self.w_speak_up = gtk.CheckButton("Use speak-up")
1665 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1666 self.w_speak_up.connect("toggled", self.toggle_speak)
1667 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1668 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1670 # speak up command
1671 self.w_speak_cmd = gtk.Entry()
1672 self.w_speak_cmd.set_width_chars(16)
1673 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1674 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1675 self.w_speak_cmd_label = gtk.Label("Speak Command")
1676 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1677 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1678 self.w_hbox3 = gtk.HBox(False, 0)
1679 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1680 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1681 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1682 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1683 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1685 # commit required
1686 self.w_commit_required = gtk.CheckButton("Commit required")
1687 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1688 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1689 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1691 # ifup required
1692 self.w_ifup_required = gtk.CheckButton("Ifup required")
1693 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1694 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1695 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1697 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1698 ### End of General tab
1700 ### Advanced tab
1701 # table to use for layout of following command configurations
1702 self.cmds_table = gtk.Table()
1704 # ifconfig command
1705 self.ifconfig_cmd = gtk.Entry()
1706 self.ifconfig_cmd.set_width_chars(32)
1707 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1708 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1709 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1710 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1711 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1712 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1713 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1714 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1716 # iwconfig command
1717 self.iwconfig_cmd = gtk.Entry()
1718 self.iwconfig_cmd.set_width_chars(32)
1719 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1720 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1721 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1722 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1723 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1724 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1725 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1726 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1728 # iwlist command
1729 self.iwlist_cmd = gtk.Entry()
1730 self.iwlist_cmd.set_width_chars(32)
1731 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1732 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1733 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1734 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1735 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1736 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1737 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1738 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1740 # route command
1741 self.route_cmd = gtk.Entry()
1742 self.route_cmd.set_width_chars(32)
1743 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1744 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1745 self.route_cmd_label = gtk.Label("Network route configure command")
1746 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1747 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1748 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1749 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1750 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1752 # log file
1753 self.logfile_entry = gtk.Entry()
1754 self.logfile_entry.set_width_chars(32)
1755 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1756 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1757 self.logfile_label = gtk.Label("Log file")
1758 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1759 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1760 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1761 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1762 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1764 # log level (spin button of integers from 0 to 50 by 5's)
1765 self.loglevel = gtk.SpinButton(gtk.Adjustment(self.confFile.get_opt_as_int('DEFAULT.loglevel'), 0, 50, 5, 5, 0), 1, 0)
1766 self.loglevel.set_update_policy(gtk.UPDATE_IF_VALID)
1767 self.loglevel.set_numeric(True)
1768 self.loglevel.set_snap_to_ticks(True)
1769 self.loglevel.set_wrap(False)
1770 self.tooltips.set_tip(self.loglevel, "How much detail to save in log file. Larger numbers provide less detail and smaller numbers, more detail.")
1771 self.loglevel.set_text(self.confFile.get_opt('DEFAULT.loglevel'))
1772 self.loglevel_label = gtk.Label("Log level")
1773 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1774 self.cmds_table.attach(self.loglevel_label, 1, 2, 6, 7, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1775 self.cmds_table.attach(self.loglevel, 2, 3, 6, 7, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1777 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1778 ### End of Advanced tab
1780 ### DHCP tab
1781 # table to use for layout of DHCP prefs
1782 self.dhcp_table = gtk.Table()
1784 self.dhcp_cmd = gtk.Entry()
1785 self.dhcp_cmd.set_width_chars(32)
1786 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1787 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1788 self.dhcp_cmd_label = gtk.Label("Command")
1789 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1790 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1791 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1792 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1794 self.dhcp_args = gtk.Entry()
1795 self.dhcp_args.set_width_chars(32)
1796 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1797 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1798 self.dhcp_args_label = gtk.Label("Arguments")
1799 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1800 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1802 self.dhcp_kill_args = gtk.Entry()
1803 self.dhcp_kill_args.set_width_chars(32)
1804 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1805 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1806 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1807 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1808 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1810 self.dhcp_timeout = gtk.Entry()
1811 self.dhcp_timeout.set_width_chars(32)
1812 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1813 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1814 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1815 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1816 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1818 self.dhcp_pidfile = gtk.Entry()
1819 self.dhcp_pidfile.set_width_chars(32)
1820 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1821 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1822 self.dhcp_pidfile_label = gtk.Label("PID file")
1823 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1824 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1826 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1827 ### End of DHCP tab
1829 ### WPA tab
1830 # table to use for layout of DHCP prefs
1831 self.wpa_table = gtk.Table()
1833 self.wpa_cmd = gtk.Entry()
1834 self.wpa_cmd.set_width_chars(32)
1835 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1836 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1837 self.wpa_cmd_label = gtk.Label("Command")
1838 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1839 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1840 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1841 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1843 self.wpa_args = gtk.Entry()
1844 self.wpa_args.set_width_chars(32)
1845 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1846 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1847 self.wpa_args_label = gtk.Label("Arguments")
1848 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1849 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1851 self.wpa_kill_args = gtk.Entry()
1852 self.wpa_kill_args.set_width_chars(32)
1853 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1854 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1855 self.wpa_kill_args_label = gtk.Label("Kill command")
1856 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1857 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1859 self.wpa_config = gtk.Entry()
1860 self.wpa_config.set_width_chars(32)
1861 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1862 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1863 self.wpa_config_label = gtk.Label("Configuration file")
1864 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1865 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1867 self.wpa_driver = gtk.Entry()
1868 self.wpa_driver.set_width_chars(32)
1869 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1870 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1871 self.wpa_driver_label = gtk.Label("Driver")
1872 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1873 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1875 self.wpa_pidfile = gtk.Entry()
1876 self.wpa_pidfile.set_width_chars(32)
1877 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1878 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1879 self.wpa_pidfile_label = gtk.Label("PID file")
1880 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1881 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1883 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1884 ### End of WPA tab
1886 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1888 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1890 #Parameters:
1892 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1894 # 'data' -- tuple - list of arbitrary arguments (not used)
1896 #Returns:
1898 # nothing
1899 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1900 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1902 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1904 #Parameters:
1906 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1908 # 'data' -- tuple - list of arbitrary arguments (not used)
1910 #Returns:
1912 # nothing
1913 def toggle_speak(self, speak_toggle, data=None):
1914 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1915 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1917 # Display preferences dialog and operate until canceled or okayed.
1919 #Parameters:
1921 # nothing
1923 #Returns:
1925 # integer -- gtk response ID
1926 def run(self):
1927 self.dialog.show_all()
1928 return self.dialog.run()
1930 # Write updated values to config file.
1932 #Parameters:
1934 # nothing
1936 #Returns:
1938 # nothing
1939 def save(self):
1940 if self.w_auto_detect.get_active():
1941 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1942 else:
1943 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1944 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1945 self.confFile.set_opt('DEFAULT.interface', interface)
1946 #self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1947 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1948 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1949 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1950 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1951 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1952 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1953 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1954 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1955 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1956 self.confFile.set_int_opt('DEFAULT.loglevel', int(self.loglevel.get_value()))
1957 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1958 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1959 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1960 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1961 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1962 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1963 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1964 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1965 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1966 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1967 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1968 try:
1969 self.confFile.write()
1970 except IOError, (error_number, error_str):
1971 if error_number == errno.ENOENT:
1972 error_dlg = ErrorDialog( self.dialog, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1973 del error_dlg
1974 else:
1975 raise IOError(error_number, error_str)
1977 # Remove preferences window.
1979 #Parameters:
1981 # nothing
1983 #Returns:
1985 # nothing
1986 def destroy(self):
1987 self.dialog.destroy()
1988 del self.dialog
1991 # Edit and return an AP profile.
1992 class profile_dialog:
1993 # Create a new profile_dialog.
1995 #Parameters:
1997 # 'parent' -- gtk.Object - Usually, the calling window.
1999 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
2001 #Returns:
2003 # profile_dialog instance
2004 def __init__( self, parent, profile ):
2005 global wifi_radar_icon
2007 # Labels
2008 self.WIFI_SET_LABEL = "WiFi Options"
2009 self.USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
2010 self.USE_IP_LABEL = "Manual network configuration"
2011 self.USE_WPA_LABEL = "Use WPA"
2012 self.NO_WPA_LABEL = "No WPA"
2013 self.CON_PP_LABEL = "Connection Commands"
2014 self.DIS_PP_LABEL = "Disconnection Commands"
2016 self.parent = parent
2017 self.profile = profile.copy()
2018 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
2019 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
2020 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
2021 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
2022 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
2023 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
2024 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2025 self.dialog.set_icon( icon )
2026 self.dialog.set_resizable( False )
2027 self.dialog.set_transient_for( self.parent.window )
2028 #self.dialog.set_size_request( 400, 400 )
2029 #################
2030 self.tooltips = gtk.Tooltips()
2032 general_table = gtk.Table()
2033 general_table.set_row_spacings(3)
2034 general_table.set_col_spacings(3)
2035 # The essid labels
2036 general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
2037 # The essid textboxes
2038 self.essid_entry = gtk.Entry(32)
2039 self.essid_entry.set_text(self.profile['essid'])
2040 general_table.attach(self.essid_entry, 1, 2, 0, 1)
2041 # Add the essid table to the dialog
2042 self.dialog.vbox.pack_start(general_table, True, True, 5)
2043 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
2045 # The bssid labels
2046 general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
2047 # The bssid textboxes
2048 self.bssid_entry = gtk.Entry(32)
2049 self.bssid_entry.set_text(self.profile['bssid'])
2050 self.bssid_entry.set_sensitive(not self.profile['roaming'])
2051 # Add the bssid table to the dialog
2052 general_table.attach(self.bssid_entry, 1, 2, 1, 2)
2053 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
2054 # Add the roaming checkbox
2055 self.roaming_cb = gtk.CheckButton('Roaming')
2056 self.roaming_cb.set_active(self.profile['roaming'])
2057 self.roaming_cb.connect("toggled", self.toggle_roaming)
2058 general_table.attach(self.roaming_cb, 1, 2, 2, 3)
2059 self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
2060 # create the WiFi expander
2061 self.wifi_expander = gtk.Expander( self.WIFI_SET_LABEL )
2062 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
2063 wifi_table = gtk.Table( 4, 2, False )
2064 wifi_table.set_row_spacings( 3 )
2065 wifi_table.set_col_spacings( 3 )
2066 # The WiFi labels
2067 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
2068 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
2069 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
2070 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
2071 # The WiFi text boxes
2072 self.mode_combo = gtk.combo_box_new_text()
2073 for mode in self.WIFI_MODES:
2074 self.mode_combo.append_text( mode )
2075 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
2076 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
2077 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
2078 self.channel_combo = gtk.combo_box_new_text()
2079 for channel in self.WIFI_CHANNELS:
2080 self.channel_combo.append_text( channel )
2081 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
2082 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
2083 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
2085 self.key_entry = gtk.Entry( 64 )
2086 self.key_entry.set_text( self.profile['key'] )
2087 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
2088 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
2090 self.security_combo = gtk.combo_box_new_text()
2091 for security in self.WIFI_SECURITY:
2092 self.security_combo.append_text( security )
2093 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
2094 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
2095 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
2096 # Add the wifi table to the expander
2097 self.wifi_expander.add( wifi_table )
2098 # Add the expander to the dialog
2099 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
2101 # create the wpa expander
2102 self.wpa_expander = gtk.Expander( self.NO_WPA_LABEL )
2103 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
2104 wpa_table = gtk.Table( 1, 2, False )
2105 wpa_table.set_row_spacings( 3 )
2106 wpa_table.set_col_spacings( 3 )
2107 # The labels
2108 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
2109 # The text boxes
2110 self.wpa_driver_entry = gtk.Entry()
2111 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
2112 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
2113 # Add the wpa table to the expander
2114 self.wpa_expander.add( wpa_table )
2115 # Add the expander to the dialog
2116 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
2118 # create the dhcp expander
2119 self.dhcp_expander = gtk.Expander( self.USE_DHCP_LABEL )
2120 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
2121 ip_table = gtk.Table( 6, 2, False )
2122 ip_table.set_row_spacings( 3 )
2123 ip_table.set_col_spacings( 3 )
2124 # The IP labels
2125 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
2126 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
2127 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
2128 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
2129 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
2130 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
2131 # The IP text boxes
2132 self.ip_entry = gtk.Entry( 15 )
2133 self.ip_entry.set_text( self.profile['ip'] )
2134 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
2135 self.netmask_entry = gtk.Entry( 15 )
2136 self.netmask_entry.set_text( self.profile['netmask'] )
2137 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
2138 self.gw_entry = gtk.Entry( 15 )
2139 self.gw_entry.set_text( self.profile['gateway'] )
2140 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
2141 self.domain_entry = gtk.Entry( 32 )
2142 self.domain_entry.set_text( self.profile['domain'] )
2143 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
2144 self.dns1_entry = gtk.Entry( 15 )
2145 self.dns1_entry.set_text( self.profile['dns1'] )
2146 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
2147 self.dns2_entry = gtk.Entry( 15 )
2148 self.dns2_entry.set_text( self.profile['dns2'] )
2149 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
2150 # Add the ip table to the expander
2151 self.dhcp_expander.add( ip_table )
2152 # Add the expander to the dialog
2153 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
2155 # create the connection-building postpre expander
2156 self.con_pp_expander = gtk.Expander( self.CON_PP_LABEL )
2157 con_pp_table = gtk.Table( 2, 2, False )
2158 con_pp_table.set_row_spacings( 3 )
2159 con_pp_table.set_col_spacings( 3 )
2160 # The labels
2161 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2162 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2163 # The text boxes
2164 self.con_prescript_entry = gtk.Entry()
2165 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
2166 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
2167 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
2168 self.con_postscript_entry = gtk.Entry()
2169 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
2170 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
2171 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
2172 # Add the pp table to the expander
2173 self.con_pp_expander.add( con_pp_table )
2174 # Add the expander to the dialog
2175 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
2177 # create the disconnection postpre expander
2178 self.dis_pp_expander = gtk.Expander( self.DIS_PP_LABEL )
2179 dis_pp_table = gtk.Table( 2, 2, False )
2180 dis_pp_table.set_row_spacings( 3 )
2181 dis_pp_table.set_col_spacings( 3 )
2182 # The labels
2183 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2184 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2185 # The text boxes
2186 self.dis_prescript_entry = gtk.Entry()
2187 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
2188 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
2189 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
2190 self.dis_postscript_entry = gtk.Entry()
2191 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
2192 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
2193 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
2194 # Add the pp table to the expander
2195 self.dis_pp_expander.add( dis_pp_table )
2196 # Add the expander to the dialog
2197 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
2199 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
2201 #Parameters:
2203 # nothing
2205 #Returns:
2207 # dictionary or None -- a profile, or None on cancel
2209 #NOTES:
2211 # Raises ValueError if an attempt is made to save an ESSID with no name.
2212 def run( self ):
2213 self.dialog.show_all()
2214 if self.dialog.run():
2215 if self.essid_entry.get_text().strip() == "":
2216 raise ValueError
2217 self.profile['known'] = True
2218 self.profile['essid'] = self.essid_entry.get_text().strip()
2219 if self.roaming_cb.get_active():
2220 self.profile['bssid'] = ''
2221 else:
2222 self.profile['bssid'] = self.bssid_entry.get_text().strip()
2223 self.profile['roaming'] = self.roaming_cb.get_active()
2224 self.profile['key'] = self.key_entry.get_text().strip()
2225 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
2226 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
2227 self.profile['encrypted'] = ( self.profile['security'] != '' )
2228 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
2229 self.profile['protocol'] = 'g'
2230 self.profile['available'] = ( self.profile['signal'] > 0 )
2231 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
2232 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
2233 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
2234 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
2235 # wpa
2236 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
2237 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
2238 # dhcp
2239 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
2240 self.profile['ip'] = self.ip_entry.get_text().strip()
2241 self.profile['netmask'] = self.netmask_entry.get_text().strip()
2242 self.profile['gateway'] = self.gw_entry.get_text().strip()
2243 self.profile['domain'] = self.domain_entry.get_text().strip()
2244 self.profile['dns1'] = self.dns1_entry.get_text().strip()
2245 self.profile['dns2'] = self.dns2_entry.get_text().strip()
2246 return self.profile
2247 return None
2249 # Remove profile dialog.
2251 #Parameters:
2253 # nothing
2255 #Returns:
2257 # nothing
2258 def destroy( self ):
2259 self.dialog.destroy()
2260 del self.dialog
2262 # Respond to roaming checkbox toggle by activating/de-activating the BSSID text entry.
2264 #Parameters:
2266 # 'roaming_toggle' -- gtk.CheckButton - The checkbox sending the signal.
2268 # 'data' -- tuple - list of arbitrary arguments (not used)
2270 #Returns:
2272 # nothing
2273 def toggle_roaming(self, roaming_toggle, data=None):
2274 self.bssid_entry.set_sensitive(not roaming_toggle.get_active())
2276 # Respond to expanding/hiding IP segment.
2278 #Parameters:
2280 # 'widget' -- gtk.Widget - The widget sending the event.
2282 # 'data' -- tuple - List of arbitrary arguments (not used)
2284 #Returns:
2286 # nothing
2287 def toggle_use_dhcp( self, widget, data = None ):
2288 expanded = self.dhcp_expander.get_expanded()
2289 if expanded:
2290 self.dhcp_expander.set_label( self.USE_IP_LABEL )
2291 else:
2292 self.dhcp_expander.set_label( self.USE_DHCP_LABEL )
2294 # Respond to expanding/hiding WPA segment.
2296 #Parameters:
2298 # 'widget' -- gtk.Widget - The widget sending the event.
2300 # 'data' -- tuple - List of arbitrary arguments (not used)
2302 #Returns:
2304 # nothing
2305 def toggle_use_wpa( self, widget, data = None ):
2306 expanded = self.wpa_expander.get_expanded()
2307 if expanded:
2308 self.wpa_expander.set_label( self.USE_WPA_LABEL )
2309 else:
2310 self.wpa_expander.set_label( self.NO_WPA_LABEL )
2312 # Return the index where item matches a cell in array.
2314 #Parameters:
2316 # 'item' -- string - Item to find in array
2318 # 'array' -- list - List in which to find match.
2320 #Returns:
2322 # integer - 0 (no match) or higher (index of match)
2323 def get_array_index( self, item, array ):
2324 try:
2325 return array.index( item.strip() )
2326 except:
2327 pass
2328 return 0
2330 # Return the value in array[ index ]
2332 #Parameters:
2334 # 'index' -- integer - The index to look up.
2336 # 'array' -- list - List in which to look up value.
2338 #Returns:
2340 # string -- empty string (no match) or looked up value
2341 def get_array_item( self, index, array ):
2342 try:
2343 return array[ index ]
2344 except:
2345 pass
2346 return ''
2349 # A simple class for putting up a "Please wait" dialog so the user
2350 # doesn't think we've forgotten about them. Implements the status interface.
2351 class StatusWindow:
2352 # Create a new StatusWindow.
2354 #Parameters:
2356 # 'parent' -- gtk.Object - Usually, the calling window.
2358 #Returns:
2360 # StatusWindow instance
2362 #NOTE:
2364 # Sample implementation of status interface. Status interface
2365 #requires .show(), .update_message(message), and .hide() methods.
2366 def __init__( self, parent ):
2367 global wifi_radar_icon
2368 self.parent = parent
2369 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2370 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2371 self.dialog.set_icon( icon )
2372 self.lbl = gtk.Label("Please wait...")
2373 self.bar = gtk.ProgressBar()
2374 self.dialog.vbox.pack_start(self.lbl)
2375 self.dialog.vbox.pack_start(self.bar)
2376 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2377 self.timer = None
2379 # Change the message displayed to the user.
2381 #Parameters:
2383 # 'message' -- string - The message to show to the user.
2385 #Returns:
2387 # nothing
2388 def update_message( self, message ):
2389 self.lbl.set_text(message)
2391 # Update the StatusWindow progress bar.
2393 #Parameters:
2395 # nothing
2397 #Returns:
2399 # True -- always return True
2400 def update_window( self ):
2401 self.bar.pulse()
2402 return True
2404 # Display and operate the StatusWindow.
2406 #Parameters:
2408 # nothing
2410 #Returns:
2412 # nothing
2413 def run( self ):
2414 pass
2416 # Show all the widgets of the StatusWindow.
2418 #Parameters:
2420 # nothing
2422 #Returns:
2424 # nothing
2425 def show( self ):
2426 self.dialog.show_all()
2427 self.timer = gobject.timeout_add(250, self.update_window)
2428 return False
2430 # Hide all the widgets of the StatusWindow.
2432 #Parameters:
2434 # nothing
2436 #Returns:
2438 # nothing
2439 def hide( self ):
2440 if self.timer:
2441 gobject.source_remove(self.timer)
2442 self.timer = None
2443 self.dialog.hide_all()
2444 return False
2446 # Remove the StatusWindow.
2448 #Parameters:
2450 # nothing
2452 #Returns:
2454 # nothing
2455 def destroy( self ):
2456 if self.timer:
2457 gobject.source_remove(self.timer)
2458 self.dialog.destroy()
2459 del self.dialog
2462 # Manage a GTK About Dialog
2463 class about_dialog(gtk.AboutDialog):
2464 # Subclass GTK AboutDialog
2466 #Parameters:
2468 # nothing
2470 #Returns:
2472 # nothing
2473 def __init__( self ):
2474 global wifi_radar_icon
2476 gtk.AboutDialog.__init__(self)
2477 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", "Ante Karamatic", "Richard Monk", "Brouard Nicolas", "Kevin Otte", "Nathanael Rebsch", "Andrea Scarpino", "Patrick Winnertz"])
2478 self.set_comments("WiFi connection manager")
2479 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2480 self.set_documenters(["Gary Case"])
2481 license = """
2482 This program is free software; you can redistribute it and/or modify
2483 it under the terms of the GNU General Public License as published by
2484 the Free Software Foundation; either version 2 of the License, or
2485 (at your option) any later version.
2487 This program is distributed in the hope that it will be useful,
2488 but WITHOUT ANY WARRANTY; without even the implied warranty of
2489 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2490 GNU General Public License for more details.
2492 You should have received a copy of the GNU General Public License
2493 along with this program; if not, write to the Free Software
2494 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2495 self.set_license(license)
2496 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2497 self.set_logo(logo)
2498 self.set_name("WiFi Radar")
2499 self.set_version(WIFI_RADAR_VERSION)
2500 self.set_website("http://wifi-radar.berlios.de")
2504 # Manage the configuration for the application, including reading and writing the config from/to a file.
2505 class ConfigFile(ConfigParser.SafeConfigParser):
2506 # Create a new ConfigFile.
2508 #Parameters:
2510 # 'filename' -- string - The configuration file's name.
2512 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2514 #Returns:
2516 # ConfigFile instance
2517 def __init__( self, filename, defaults, raw=False ):
2518 self.filename = filename
2519 self.raw = raw
2520 self.auto_profile_order = []
2521 ConfigParser.SafeConfigParser.__init__(self, defaults)
2523 # Set the contents of a section to values from a dictionary.
2525 #Parameters:
2527 # 'section_name' -- string - Configuration file section.
2529 # 'section_dict' -- dictionary - Values to add to section.
2531 #Returns:
2533 # nothing
2534 def set_section( self, section_name, section_dict ):
2535 try:
2536 self.add_section(section_name)
2537 except ConfigParser.DuplicateSectionError:
2538 pass
2539 for key in section_dict.keys():
2540 if type(section_dict[key]) == BooleanType:
2541 self.set_bool_opt(section_name + "." + key, section_dict[key])
2542 elif type(section_dict[key]) == IntType:
2543 self.set_int_opt(section_name + "." + key, section_dict[key])
2544 elif type(section_dict[key]) == FloatType:
2545 self.set_float_opt(section_name + "." + key, section_dict[key])
2546 else:
2547 self.set_opt(section_name + "." + key, section_dict[key])
2549 # Return the profile recorded in the specified section.
2551 #Parameters:
2553 # 'section_name' -- string - Configuration file section.
2555 #Returns:
2557 # dictionary or None - The specified profile or None if not found
2558 def get_profile( self, section_name ):
2559 if section_name in self.profiles():
2560 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' ]
2561 bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
2562 int_types = [ 'signal' ]
2563 profile = get_new_profile()
2564 for option in bool_types:
2565 option_tmp = self.get_opt_as_bool(section_name + "." + option)
2566 if option_tmp:
2567 profile[option] = option_tmp
2568 for option in int_types:
2569 option_tmp = self.get_opt_as_int(section_name + "." + option)
2570 if option_tmp:
2571 profile[option] = option_tmp
2572 for option in str_types:
2573 option_tmp = self.get_opt(section_name + "." + option)
2574 if option_tmp:
2575 profile[option] = option_tmp
2576 return profile
2577 return None
2579 # Get a config option and handle exceptions.
2581 #Parameters:
2583 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2584 # period and the option key. (E.g. "DEFAULT.interface")
2586 #Returns:
2588 # string or None - option value as string or None on failure
2589 def get_opt( self, option_path ):
2590 #print "ConfigFile.get_opt: ", option_path
2591 (section, option) = option_path.split('.')
2592 try:
2593 return self.get(section, option, self.raw)
2594 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2595 return None
2597 # Get a config option and return as a boolean type.
2599 #Parameters:
2601 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2602 # period and the option key. (E.g. "DEFAULT.interface")
2604 #Returns:
2606 # boolean - option value as boolean
2607 def get_opt_as_bool( self, option_path ):
2608 option = self.get_opt(option_path)
2609 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2610 return option
2611 if option == 'True':
2612 return True
2613 if option == 'False':
2614 return False
2615 raise ValueError, 'boolean option was not True or False'
2617 # Get a config option and return as an integer type.
2619 #Parameters:
2621 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2622 # period and the option key. (E.g. "DEFAULT.interface")
2624 #Returns:
2626 # integer- option value as integer
2627 def get_opt_as_int( self, option_path ):
2628 return int(float(self.get_opt(option_path)))
2630 # Convert boolean type to string and set config option.
2632 #Parameters:
2634 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2635 # period and the option key. (E.g. "DEFAULT.interface")
2637 # 'value' -- boolean - Value to set.
2639 #Returns:
2641 # nothing
2642 def set_bool_opt( self, option_path, value ):
2643 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2644 value == 'True'
2645 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2646 value == 'False'
2647 else:
2648 raise ValueError, 'cannot convert value to string'
2649 self.set_opt(option_path, repr(value))
2651 # Convert integer type to string and set config option.
2653 #Parameters:
2655 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2656 # period and the option key. (E.g. "DEFAULT.interface")
2658 # 'value' -- integer - Value to set.
2660 #Returns:
2662 # nothing
2663 def set_int_opt( self, option_path, value ):
2664 if not isinstance(value, IntType):
2665 raise ValueError, 'value is not an integer'
2666 self.set_opt(option_path, repr(value))
2668 # Convert float type to string and set config option.
2670 #Parameters:
2672 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2673 # period and the option key. (E.g. "DEFAULT.interface")
2675 # 'value' -- float - Value to set.
2677 #Returns:
2679 # nothing
2680 def set_float_opt( self, option_path, value ):
2681 if not isinstance(value, FloatType):
2682 raise ValueError, 'value is not a float'
2683 self.set_opt(option_path, repr(int(value)))
2685 # Set a config option while handling exceptions.
2687 #Parameters:
2689 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2690 # period and the option key. (E.g. "DEFAULT.interface")
2692 # 'value' -- string - Value to set.
2694 #Returns:
2696 # nothing
2697 def set_opt( self, option_path, value ):
2698 (section, option) = option_path.split('.')
2699 try:
2700 self.set(section, option, value)
2701 except ConfigParser.NoSectionError:
2702 self.add_section(section)
2703 self.set_opt(option_path, value)
2705 # Return a list of the section names which denote AP profiles.
2707 #Parameters:
2709 # nothing
2711 #Returns:
2713 # list - profile names
2714 def profiles( self ):
2715 profile_list = []
2716 for section in self.sections():
2717 if ':' in section:
2718 profile_list.append(section)
2719 return profile_list
2721 # Read configuration file from disk into instance variables.
2723 #Parameters:
2725 # nothing
2727 #Returns:
2729 # nothing
2730 def read( self ):
2731 fp = open( self.filename, "r" )
2732 self.readfp(fp)
2733 # convert the auto_profile_order to a list for ordering
2734 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2735 for ap in self.profiles():
2736 self.set_bool_opt( ap + '.known', True)
2737 if ap in self.auto_profile_order: continue
2738 self.auto_profile_order.append( ap )
2739 fp.close()
2741 # Write configuration file to disk from instance variables. Copied from
2742 # ConfigParser and modified to write options in alphabetical order.
2744 #Parameters:
2746 # nothing
2748 #Returns:
2750 # nothing
2751 def write( self ):
2752 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2753 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2754 fp = open( self.filename, "w" )
2755 # write DEFAULT section first
2756 if self._defaults:
2757 fp.write("[DEFAULT]\n")
2758 for key in sorted(self._defaults.keys()):
2759 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2760 fp.write("\n")
2761 # write non-profile sections first
2762 for section in self._sections:
2763 if section not in self.profiles():
2764 fp.write("[%s]\n" % section)
2765 for key in sorted(self._sections[section].keys()):
2766 if key != "__name__":
2767 fp.write("%s = %s\n" %
2768 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2769 fp.write("\n")
2770 # write profile sections
2771 for section in self._sections:
2772 if section in self.profiles():
2773 fp.write("[%s]\n" % section)
2774 for key in sorted(self._sections[section].keys()):
2775 if key != "__name__":
2776 fp.write("%s = %s\n" %
2777 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2778 fp.write("\n")
2779 fp.close()
2783 # Load our conf file and known profiles
2784 # Defaults, these may get overridden by values found in the conf file.
2785 config_defaults = { # The network interface you use.
2786 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2787 'interface': "auto_detect",
2788 # How long should the scan for access points last?
2789 #'scan_timeout': '5',
2790 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2791 # Set the speak_up option to false if you do not have or want this.
2792 'speak_command': '/usr/bin/say',
2793 # Should I speak up when connecting to a network? (If you have a speech command)
2794 'speak_up': 'False',
2795 # You may set this to true for cards that require a "commit" command with iwconfig
2796 'commit_required': 'False',
2797 # You may set this to true for cards that require the interface to be brought up first
2798 'ifup_required': 'False',
2799 # set the location and verbosity of the log file
2800 'logfile': '/var/log/wifi-radar.log',
2801 'loglevel': '50',
2802 # Set the location of several important programs
2803 'iwlist_command': '/sbin/iwlist',
2804 'iwconfig_command': '/sbin/iwconfig',
2805 'ifconfig_command': '/sbin/ifconfig',
2806 'route_command': '/sbin/route',
2807 'auto_profile_order': '[]',
2808 'version': WIFI_RADAR_VERSION }
2810 config_dhcp = { # DHCP client
2811 'command': '/sbin/dhcpcd',
2812 # How long to wait for an IP addr from DHCP server
2813 'timeout': '30',
2814 # Arguments to use with DHCP client on connect
2815 'args': '-D -o -i dhcp_client -t %(timeout)s',
2816 # Argument to use with DHCP client on disconnect
2817 'kill_args': '-k',
2818 # The file where DHCP client PID is written
2819 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2821 config_wpa = { # WPA Supplicant
2822 'command': '/usr/sbin/wpa_supplicant',
2823 # Arguments to use with WPA Supplicant on connect
2824 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2825 # Arguments to use with WPA Supplicant on disconnect
2826 'kill_command': '',
2827 # Where the WPA Supplicant config file can be found
2828 'configuration': '/etc/wpa_supplicant.conf',
2829 # Driver to use with WPA Supplicant
2830 'driver': 'wext',
2831 # The file where WPA Supplicant PID is written
2832 'pidfile': '/var/run/wpa_supplicant.pid' }
2834 # initialize config, with defaults
2835 confFile = ConfigFile(CONF_FILE, config_defaults)
2836 confFile.set_section("DHCP", config_dhcp)
2837 confFile.set_section("WPA", config_wpa)
2839 if not os.path.isfile( CONF_FILE ):
2840 confFile.set_bool_opt('DEFAULT.new_file', True)
2841 else:
2842 if not os.access(CONF_FILE, os.R_OK):
2843 print "Can't open " + CONF_FILE + "."
2844 print "Are you root?"
2845 sys.exit()
2846 confFile.read()
2849 ####################################################################################################
2850 # Embedded Images
2851 wifi_radar_icon = [ ""
2852 "GdkP"
2853 "\0\0\22""7"
2854 "\2\1\0\2"
2855 "\0\0\1\214"
2856 "\0\0\0c"
2857 "\0\0\0O"
2858 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2859 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2860 "\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"
2861 "\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"
2862 "\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"
2863 "\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"
2864 "\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"
2865 "\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"
2866 "\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"
2867 "\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"
2868 "\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"
2869 "\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"
2870 "\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"
2871 "\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"
2872 "\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"
2873 "\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"
2874 "\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"
2875 "\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"
2876 "\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"
2877 "\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"
2878 "\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"
2879 "\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"
2880 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2881 "\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"
2882 "\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"
2883 "\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"
2884 "\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"
2885 "\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"
2886 "\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"
2887 "\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"
2888 "\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"
2889 "\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"
2890 "\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"
2891 "\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"
2892 "\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"
2893 "\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"
2894 "\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"
2895 "\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"
2896 "\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"
2897 "\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"
2898 "\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"
2899 "\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"
2900 "\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"
2901 "\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"
2902 "\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"
2903 "\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"
2904 "\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"
2905 "\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"
2906 "\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"
2907 "\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"
2908 "\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"
2909 "\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"
2910 "\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"
2911 "\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"
2912 "\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"
2913 "\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"
2914 "\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"
2915 "\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"
2916 "\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"
2917 "\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"
2918 "\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"
2919 "\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"
2920 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2921 "\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"
2922 "\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"
2923 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2924 "\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"
2925 "\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"
2926 "\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"
2927 "\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"
2928 "\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"
2929 "\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"
2930 "\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"
2931 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2932 "\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"
2933 "\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"
2934 "\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"
2935 "\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"
2936 "\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"
2937 "\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"
2938 "|\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"
2939 "\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"
2940 "\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"
2941 "\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"
2942 "\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"
2943 "\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"
2944 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2945 "\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"
2946 "\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"
2947 "\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"
2948 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2949 "\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"
2950 "\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"
2951 "\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"
2952 "\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"
2953 "\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"
2954 "\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"
2955 "\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"
2956 "\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"
2957 "\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"
2958 "\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"
2959 "\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"
2960 "\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"
2961 "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"
2962 "\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"
2963 "\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"
2964 "\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"
2965 "\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"
2966 "\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"
2967 "\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"
2968 "\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"
2969 "\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"
2970 "\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"
2971 "\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"
2972 "\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"
2973 "\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"
2974 "\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"
2975 "\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"
2976 "\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|"
2977 "\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"
2978 "\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"
2979 "\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"
2980 "\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"
2981 "\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"
2982 "\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"
2983 "\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"
2984 "\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"
2985 "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"
2986 "\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"
2987 "\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"
2988 "\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"
2989 "\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"
2990 "\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"
2991 "\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"
2992 "\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"
2993 "\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"
2994 "\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"
2995 "\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"
2996 "\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"
2997 "\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"
2998 "\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"
2999 "\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"
3000 "\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"
3001 "\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"
3002 "\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"
3003 "\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"
3004 "\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"
3005 "\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"
3006 "\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"
3007 "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"
3008 "\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"
3009 "\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"
3010 "\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"
3011 "\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"
3012 "\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"
3013 "\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"
3014 "\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"
3015 "\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"
3016 "\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"
3017 "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"
3018 "\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"
3019 "\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"
3020 "\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"
3021 "\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"
3022 "\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"
3023 "\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"
3024 "\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"
3025 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
3026 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
3027 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
3028 "\0"]
3030 known_profile_icon = [ ""
3031 "GdkP"
3032 "\0\0\5""0"
3033 "\2\1\0\2"
3034 "\0\0\0P"
3035 "\0\0\0\24"
3036 "\0\0\0\24"
3037 "\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"
3038 "\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"
3039 "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"
3040 "\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"
3041 "\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"
3042 "\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"
3043 "\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"
3044 "\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"
3045 "\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"
3046 "\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"
3047 "\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"
3048 "\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"
3049 "\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"
3050 "\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"
3051 "\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"
3052 "\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"
3053 "\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"
3054 "\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"
3055 "\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"
3056 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
3057 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
3058 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
3059 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
3060 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
3061 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
3062 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
3063 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
3064 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
3065 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
3066 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
3067 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
3068 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
3069 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
3070 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
3071 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
3072 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
3073 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
3074 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
3075 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
3076 "\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"
3077 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
3078 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
3079 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
3080 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
3081 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
3082 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
3083 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
3084 "\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"
3085 "\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"
3086 "\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"
3087 "\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"
3088 "\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"]
3090 unknown_profile_icon = [ ""
3091 "GdkP"
3092 "\0\0\5\22"
3093 "\2\1\0\2"
3094 "\0\0\0P"
3095 "\0\0\0\24"
3096 "\0\0\0\24"
3097 "\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"
3098 "\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"
3099 "\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"
3100 "\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"
3101 "(\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"
3102 "\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"
3103 "#\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"
3104 "\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"
3105 "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"
3106 "\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"
3107 "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"
3108 "\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"
3109 "\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"
3110 "\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"
3111 "\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"
3112 "\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"
3113 "\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"
3114 "\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"
3115 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
3116 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
3117 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
3118 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
3119 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
3120 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
3121 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
3122 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
3123 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
3124 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
3125 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
3126 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
3127 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
3128 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
3129 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
3130 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
3131 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
3132 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
3133 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
3134 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
3135 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
3136 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
3137 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
3138 "\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"
3139 "\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"
3140 "\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"
3141 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
3143 signal_xpm_barely = [
3144 "20 20 10 1",
3145 " c None",
3146 ". c #C6C6C6",
3147 "+ c #CCCCCC",
3148 "@ c #DBDBDB",
3149 "# c #D3D3D3",
3150 "$ c #A9B099",
3151 "% c #95A173",
3152 "& c #6B8428",
3153 "* c #B4B7AC",
3154 "= c #80924D",
3155 " .+++.",
3156 " +@@@+",
3157 " +@@@+",
3158 " +@@@+",
3159 " +@@@+",
3160 " .++++#@@@+",
3161 " +@@@@@@@@+",
3162 " +@@@@@@@@+",
3163 " +@@@@@@@@+",
3164 " +@@@@@@@@+",
3165 " $%%%%#@@@@@@@@+",
3166 " %&&&&@@@@@@@@@+",
3167 " %&&&&@@@@@@@@@+",
3168 " %&&&&@@@@@@@@@+",
3169 " %&&&&@@@@@@@@@+",
3170 "*%%%%=&&&&@@@@@@@@@+",
3171 "%&&&&&&&&&@@@@@@@@@+",
3172 "%&&&&&&&&&@@@@@@@@@+",
3173 "%&&&&&&&&&@@@@@@@@@+",
3174 "*%%%%%%%%%+++++++++."
3178 signal_xpm_best = [
3179 "20 20 6 1",
3180 " c None",
3181 ". c #9DAABF",
3182 "+ c #7B96BF",
3183 "@ c #386EBF",
3184 "# c #5982BF",
3185 "$ c #AEB4BF",
3186 " .+++.",
3187 " +@@@+",
3188 " +@@@+",
3189 " +@@@+",
3190 " +@@@+",
3191 " .++++#@@@+",
3192 " +@@@@@@@@+",
3193 " +@@@@@@@@+",
3194 " +@@@@@@@@+",
3195 " +@@@@@@@@+",
3196 " .++++#@@@@@@@@+",
3197 " +@@@@@@@@@@@@@+",
3198 " +@@@@@@@@@@@@@+",
3199 " +@@@@@@@@@@@@@+",
3200 " +@@@@@@@@@@@@@+",
3201 "$++++#@@@@@@@@@@@@@+",
3202 "+@@@@@@@@@@@@@@@@@@+",
3203 "+@@@@@@@@@@@@@@@@@@+",
3204 "+@@@@@@@@@@@@@@@@@@+",
3205 "$++++++++++++++++++."
3208 signal_xpm_none = [
3209 "20 20 6 1",
3210 " c None",
3211 ". c #C6C6C6",
3212 "+ c #CCCCCC",
3213 "@ c #DBDBDB",
3214 "# c #D3D3D3",
3215 "$ c #C2C2C2",
3216 " .+++.",
3217 " +@@@+",
3218 " +@@@+",
3219 " +@@@+",
3220 " +@@@+",
3221 " .++++#@@@+",
3222 " +@@@@@@@@+",
3223 " +@@@@@@@@+",
3224 " +@@@@@@@@+",
3225 " +@@@@@@@@+",
3226 " .++++#@@@@@@@@+",
3227 " +@@@@@@@@@@@@@+",
3228 " +@@@@@@@@@@@@@+",
3229 " +@@@@@@@@@@@@@+",
3230 " +@@@@@@@@@@@@@+",
3231 "$++++#@@@@@@@@@@@@@+",
3232 "+@@@@@@@@@@@@@@@@@@+",
3233 "+@@@@@@@@@@@@@@@@@@+",
3234 "+@@@@@@@@@@@@@@@@@@+",
3235 "$++++++++++++++++++."
3238 signal_xpm_ok = [
3239 "20 20 10 1",
3240 " c None",
3241 ". c #C6C6C6",
3242 "+ c #CCCCCC",
3243 "@ c #DBDBDB",
3244 "# c #A1A5B2",
3245 "$ c #848DA5",
3246 "% c #D3D3D3",
3247 "& c #4A5B8C",
3248 "* c #677498",
3249 "= c #B0B2B8",
3250 " .+++.",
3251 " +@@@+",
3252 " +@@@+",
3253 " +@@@+",
3254 " +@@@+",
3255 " #$$$$%@@@+",
3256 " $&&&&@@@@+",
3257 " $&&&&@@@@+",
3258 " $&&&&@@@@+",
3259 " $&&&&@@@@+",
3260 " #$$$$*&&&&@@@@+",
3261 " $&&&&&&&&&@@@@+",
3262 " $&&&&&&&&&@@@@+",
3263 " $&&&&&&&&&@@@@+",
3264 " $&&&&&&&&&@@@@+",
3265 "=$$$$*&&&&&&&&&@@@@+",
3266 "$&&&&&&&&&&&&&&@@@@+",
3267 "$&&&&&&&&&&&&&&@@@@+",
3268 "$&&&&&&&&&&&&&&@@@@+",
3269 "=$$$$$$$$$$$$$$++++."
3273 signal_xpm_low = [
3274 "20 20 8 1",
3275 " c None",
3276 ". c #C6C6C6",
3277 "+ c #CCCCCC",
3278 "@ c #DBDBDB",
3279 "# c #D3D3D3",
3280 "$ c #BFB0B5",
3281 "% c #C18799",
3282 "& c #C54F74",
3283 " .+++.",
3284 " +@@@+",
3285 " +@@@+",
3286 " +@@@+",
3287 " +@@@+",
3288 " .++++#@@@+",
3289 " +@@@@@@@@+",
3290 " +@@@@@@@@+",
3291 " +@@@@@@@@+",
3292 " +@@@@@@@@+",
3293 " .++++#@@@@@@@@+",
3294 " +@@@@@@@@@@@@@+",
3295 " +@@@@@@@@@@@@@+",
3296 " +@@@@@@@@@@@@@+",
3297 " +@@@@@@@@@@@@@+",
3298 "$%%%%#@@@@@@@@@@@@@+",
3299 "%&&&&@@@@@@@@@@@@@@+",
3300 "%&&&&@@@@@@@@@@@@@@+",
3301 "%&&&&@@@@@@@@@@@@@@+",
3302 "$%%%%++++++++++++++."
3306 ####################################################################################################
3307 # Make so we can be imported
3308 if __name__ == "__main__":
3309 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
3310 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
3311 else:
3312 import gtk, gobject
3313 gtk.gdk.threads_init()
3314 apQueue = Queue.Queue(100)
3315 commQueue = Queue.Queue(2)
3317 logger = logging.getLogger("wrlog")
3318 logger.setLevel(confFile.get_opt_as_int('DEFAULT.loglevel'))
3319 fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
3320 fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
3321 logger.addHandler(fileLogHandler)
3322 consoleLogHandler = logging.StreamHandler()
3323 consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
3324 consoleLogHandler.setLevel(logging.CRITICAL)
3325 logger.addHandler(consoleLogHandler)
3326 if __debug__:
3327 logger.setLevel(logging.INFO)
3329 exit_event = threading.Event()
3330 exit_event.clear()
3331 threading.Thread(None, scanning_thread, None, (confFile, apQueue, commQueue, logger, exit_event)).start()
3332 main_radar_window = radar_window(confFile, apQueue, commQueue, logger, exit_event)
3333 gobject.timeout_add( 500, main_radar_window.update_window )
3334 main_radar_window.main()