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