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