Rename radar_window.update_plist_items() to radar_window.update_list()
[wifi-radar.git] / wifi-radar
blobb38d8dd1737217fbe9448e19fdb74ad02284354d
1 #!/usr/bin/python
3 # A wireless profile manager for Linux
5 # Originally created for x1000 Linux:
6 # http://x1000.bitbuilder.com
8 # Created by:
9 # Ahmad Baitalmal <ahmad@baitalmal.com>
11 # Maintained 2006-2009 by:
12 # Brian Elliott Finley <brian@thefinleys.com>
14 # Maintained by:
15 # Sean Robinson <seankrobinson@gmail.com>
17 # License:
18 # GPL
20 # http://wifi-radar.berlios.de
22 # See CREDITS file for more contributors.
23 # See ChangeLog file for, well, changes.
25 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
26 # turn on console debugging.
28 import ConfigParser
29 import errno
30 import gtk
31 import logging
32 import logging.handlers
33 import os
34 import Queue
35 import re
36 import string
37 import sys
38 import threading
39 from signal import SIGTERM
40 from subprocess import call, Popen, PIPE
41 from time import sleep
42 from types import *
44 WIFI_RADAR_VERSION = "0.0.0"
47 # Where the conf file should live could be different for your distro. Please change
48 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
50 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
52 os.environ['LC_MESSAGES'] = 'C'
55 ####################################################################################################
56 ####################################################################################################
58 # Sets the interface to the specified network device
60 #Parameters:
62 # 'device' -- string - The network device to use
64 #Returns:
66 # nothing
67 def set_network_device( device ):
68 #print "set_network_device: ", device
69 if device != "auto_detect":
70 confFile.set_opt('DEFAULT.interface', device)
71 else: # auto detect network device
72 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
73 # If no devices are found, default to eth1.
74 # call iwconfig command and read output
75 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
76 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
77 if len(wireless_devices) > 0:
78 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
79 #else:
80 #print "No wifi-device found. Exiting."
81 #sys.exit()
83 # Return a blank profile
85 #Parameters:
87 # none
89 #Returns:
91 # dictionary -- An AP profile with defaults set.
92 def get_new_profile():
93 return { 'known': False,
94 'available': False,
95 'encrypted': False,
96 'essid': '',
97 'bssid': '',
98 'roaming': False,
99 'protocol': 'g',
100 'signal': 0,
101 'channel': 'auto',
102 'con_prescript': '',
103 'con_postscript': '',
104 'dis_prescript': '',
105 'dis_postscript': '',
106 'key': '',
107 'mode': 'auto',
108 'security': '',
109 'use_wpa': False,
110 'wpa_driver': '',
111 'use_dhcp': True,
112 'ip': '',
113 'netmask': '',
114 'gateway': '',
115 'domain': '',
116 'dns1': '',
117 'dns2': ''
120 # Combine essid and bssid to make a config file section name
122 #Parameters:
124 # 'essid' -- string - AP ESSID
126 # 'bssid' -- string - AP BSSID
128 #Returns:
130 # string -- the bssid concatenated to a colon, concatenated to the essid
131 def make_section_name( essid, bssid ):
132 return essid + ':' + bssid
134 # Split a config file section name into an essid and a bssid
136 #Parameters:
138 # 'section' -- string - Config file section name
140 #Returns:
142 # list -- the essid and bssid
143 def split_section_name( section ):
144 parts = re.split(':', section)
145 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
147 # Run commands through the shell
149 #Parameters:
151 # 'command' -- tuple - The command and arguments to run.
153 # 'environment' -- dictionary - Environment variables (as keys) and their values.
155 #Returns:
157 # boolean -- True on success, otherwise, False
158 def shellcmd( command, environment = None ):
159 try:
160 env_tmp = os.environ
161 env_tmp.update(environment)
162 command = ' '.join(command)
163 return_code = call(command, shell=True, env=env_tmp)
164 if return_code >= 0:
165 return True
166 else:
167 print >>sys.stderr, "Child was terminated by signal", -return_code
168 except OSError, exception:
169 print >>sys.stderr, "Execution failed:", exception
170 return False
172 # Speak feedback message to user
174 #Parameters:
176 # 'words' -- string - Message to speak to user
178 #Returns:
180 # nothing
181 def say( words ):
182 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
183 words = words.replace( "\"", "\\\"" )
184 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
186 # Scan for a limited time and return AP names and bssid found.
187 # Access points we find will be put on the outgoing Queue, apQueue.
189 #Parameters:
191 # 'confFile' -- ConfigFile - Config file object
193 # 'apQueue' -- Queue - Queue on which to put AP profiles
195 # 'commandQueue' -- Queue - Queue from which to read commands
197 # 'logger' -- Logger - Python's logging facility
199 #Returns:
201 # nothing
202 def scanning_thread( confFile, apQueue, commandQueue, logger ):
203 logger.debug("Begin thread.")
204 # Setup our essid pattern matcher
205 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
206 bssid_pattern = re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
207 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S )
208 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
209 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
210 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
211 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
213 access_points = {}
214 command = "scan"
215 while True:
216 try:
217 command = commandQueue.get_nowait()
218 logger.debug("received command: %s" % ( command, ))
219 command_read = True
220 except Queue.Empty:
221 command_read = False
222 if command == "scan":
223 #logger.debug("Beginning scan pass")
224 # Some cards need to have the interface up to scan
225 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
226 # call ifconfig command and wait for return
227 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
228 # update the signal strengths
229 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
230 #logger.debug("Current IP: %s\nCurrent ESSID: %s\nCurrent BSSID: %s" % (get_current_ip(), get_current_essid(), get_current_bssid()))
231 # zero out the signal levels for all access points
232 for bssid in access_points:
233 access_points[bssid]['signal'] = 0
234 # split the scan data based on the address line
235 hits = scandata.split(' - ')
236 for hit in hits:
237 # set the defaults for profile template
238 profile = get_new_profile()
239 m = essid_pattern.search( hit )
240 if m:
241 # we found an essid
242 profile['essid'] = m.groups()[1]
243 m = bssid_pattern.search( hit ) # get BSSID from scan
244 if m: profile['bssid'] = m.groups()[1]
245 m = protocol_pattern.search( hit ) # get protocol from scan
246 if m: profile['protocol'] = m.groups()[1]
247 m = mode_pattern.search( hit ) # get mode from scan
248 if m: profile['mode'] = m.groups()[1]
249 m = channel_pattern.search( hit ) # get channel from scan
250 if m: profile['channel'] = m.groups()[1]
251 m = enckey_pattern.search( hit ) # get encryption key from scan
252 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
253 m = signal_pattern.search( hit ) # get signal strength from scan
254 if m: profile['signal'] = m.groups()[1]
255 access_points[ profile['bssid'] ] = profile
256 for bssid in access_points:
257 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
258 # Put all, now or previously, sensed access_points into apQueue
259 try:
260 apQueue.put_nowait( access_points[bssid] )
261 except Queue.Full:
262 pass
263 elif command == "exit":
264 logger.debug("Exiting.")
265 return
266 if command_read: commandQueue.task_done()
267 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
268 sleep( 3 )
269 else:
270 sleep( 1 )
273 # Manage a connection; including reporting connection state,
274 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
275 class ConnectionManager():
276 # Create a new connection manager which can read a config file and send to scanning thread
277 # command Queue. A new manager checks for a pre-existing connection and takes
278 # its AP profile from the ESSID and BSSID to which it is currently attached.
280 #Parameters:
282 # 'confFile' -- ConfigFile - Config file object
284 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
286 # 'logger' -- Logger - Python's logging facility
288 #Returns:
290 # ConnectionManager instance
291 def __init__( self, confFile, commandQueue, logger ):
292 self.confFile = confFile
293 self.commQueue = commandQueue
294 self.logger = logger
295 # is connection running?
296 self.state = False
297 if self.get_current_ip():
298 self.state = True
299 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
301 # Change the interface state: up or down.
303 #Parameters:
305 # 'state' -- string - The state to which to change the interface.
307 #Returns:
309 # nothing
310 def if_change( self, state ):
311 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
312 self.logger.debug("changing interface state to %s" % ( state, ))
313 # call ifconfig command and wait for return
314 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
316 # Connect to the specified AP.
318 #Parameters:
320 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
322 # 'status' -- status implementer - Object which implements status interface.
324 #Returns:
326 # nothing
327 def connect_to_network( self, profile, status ):
328 self.profile = profile
329 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
330 say( msg )
331 self.logger.debug(msg)
332 # ready to dance
333 # Let's run the connection prescript
334 if self.profile['con_prescript'].strip() != '':
335 # got something to execute
336 # run connection prescript through shell and wait for return
337 self.logger.debug("executing connection prescript: %s" % ( self.profile['con_prescript'], ))
338 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
339 status.show()
340 # Some cards need to have the interface up
341 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
342 self.if_change('up')
343 # Start building iwconfig command line, command
344 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
345 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
346 # Setting essid
347 iwconfig_command.append( 'essid' )
348 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
349 # Setting nick
350 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
351 # Setting key
352 iwconfig_command.append( 'key' )
353 if self.profile['key'] == '':
354 iwconfig_command.append( 'off' )
355 else:
356 iwconfig_command.append( "'" + self.profile['key'] + "'" )
357 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
358 # Setting mode
359 if self.profile['mode'].lower() == 'master':
360 self.profile['mode'] = 'Managed'
361 iwconfig_command.append( 'mode' )
362 iwconfig_command.append( self.profile['mode'] )
363 # Setting channel
364 if self.profile['channel'] != '':
365 iwconfig_command.append( 'channel' )
366 iwconfig_command.append( self.profile['channel'] )
367 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
368 iwconfig_command.append( 'ap' )
369 iwconfig_command.append( self.profile['bssid'] )
370 # Some cards require a commit
371 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
372 self.logger.debug("iwconfig_args %s " % ( iwconfig_args, ))
373 iwconfig_command.append( 'commit' )
374 # call iwconfig command and wait for return
375 if not shellcmd(iwconfig_command): return
376 # Now normal network stuff
377 # Kill off any existing DHCP clients running
378 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
379 self.logger.debug("Killing existing DHCP...")
380 try:
381 if self.confFile.get_opt('DHCP.kill_args') != '':
382 # call DHCP client kill command and wait for return
383 self.logger.debug("%s %s", ( self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args'), ))
384 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
385 else:
386 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
387 except OSError:
388 print "failed to kill DHCP client"
389 sys.exit()
390 finally:
391 print "Stale pid file. Removing..."
392 os.remove(self.confFile.get_opt('DHCP.pidfile'))
393 # Kill off any existing WPA supplicants running
394 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
395 self.logger.debug("Killing existing WPA supplicant...")
396 try:
397 if not self.confFile.get_opt('WPA.kill_command') != '':
398 # call WPA supplicant kill command and wait for return
399 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
400 else:
401 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
402 except OSError:
403 print "failed to kill WPA supplicant"
404 sys.exit()
405 finally:
406 print "Stale pid file. Removing..."
407 os.remove(self.confFile.get_opt('WPA.pidfile'))
408 # Begin WPA supplicant
409 if self.profile['use_wpa'] :
410 self.logger.debug("WPA args: %s" % ( self.confFile.get_opt('WPA.args'), ))
411 status.update_message("WPA supplicant starting")
412 if sys.modules.has_key("gtk"):
413 while gtk.events_pending():
414 gtk.main_iteration(False)
415 # call WPA supplicant command and do not wait for return
416 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
417 if self.profile['use_dhcp'] :
418 self.logger.debug("Disable iwlist while dhcp in progress...")
419 try:
420 self.commQueue.put("pause")
421 except Queue.Full:
422 pass
423 status.update_message("Acquiring IP Address (DHCP)")
424 if sys.modules.has_key("gtk"):
425 while gtk.events_pending():
426 gtk.main_iteration(False)
427 # call DHCP client command and do not wait for return
428 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
429 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
430 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
431 dhcp_proc = Popen(dhcp_command, stdout=None)
432 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
433 tick = 0.25
434 waiting = dhcp_proc.poll()
435 while waiting == None:
436 waiting = dhcp_proc.poll()
437 if timer < 0:
438 os.kill(dhcp_proc.pid, SIGTERM)
439 break
440 if sys.modules.has_key("gtk"):
441 while gtk.events_pending():
442 gtk.main_iteration(False)
443 timer -= tick
444 sleep(tick)
445 # Re-enable iwlist
446 try:
447 self.commQueue.put("scan")
448 except Queue.Full:
449 pass
450 if not self.get_current_ip():
451 status.update_message("Could not get IP address!")
452 if sys.modules.has_key("gtk"):
453 while gtk.events_pending():
454 gtk.main_iteration(False)
455 sleep(1)
456 if self.state:
457 self.disconnect_interface()
458 status.hide()
459 return
460 else:
461 status.update_message("Got IP address. Done.")
462 self.state = True
463 if sys.modules.has_key("gtk"):
464 while gtk.events_pending():
465 gtk.main_iteration(False)
466 sleep(2)
467 else:
468 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'] )
469 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
470 resolv_contents = ''
471 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
472 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
473 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
474 if ( resolv_contents != '' ):
475 resolv_file=open('/etc/resolv.conf', 'w')
476 resolv_file.write(s)
477 resolv_file.close
478 if not shellcmd([ifconfig_command]): return
479 if not shellcmd([route_command]): return
480 self.state = True
481 # Let's run the connection postscript
482 con_postscript = self.profile['con_postscript']
483 if self.profile['con_postscript'].strip() != '':
484 self.logger.debug("executing connection postscript: %s" % ( self.profile['con_postscript'], ))
485 shellcmd([self.profile['con_postscript']],
486 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
487 "WIFIRADAR_ESSID": self.get_current_essid() or '',
488 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
489 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
492 status.hide()
494 # Disconnect from the AP with which a connection has been established/attempted.
496 #Parameters:
498 # nothing
500 #Returns:
502 # nothing
503 def disconnect_interface( self ):
504 msg = "Disconnecting"
505 say( msg )
506 self.logger.debug(msg)
507 # Pause scanning while manipulating card
508 try:
509 self.commQueue.put("pause")
510 except Queue.Full:
511 pass
512 if self.state:
513 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
514 # Let's run the disconnection prescript
515 if self.profile['dis_prescript'].strip() != '':
516 self.logger.debug("executing disconnection prescript: %s" % ( self.profile['dis_prescript'], ))
517 shellcmd([self.profile['dis_prescript']],
518 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
519 "WIFIRADAR_ESSID": self.get_current_essid() or '',
520 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
521 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
524 self.logger.debug("Kill off any existing DHCP clients running...")
525 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
526 self.logger.debug("Killing existing DHCP...")
527 try:
528 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
529 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
530 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
531 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
532 self.logger.debug("DHCP command: %s" % ( dhcp_command, ))
533 # call DHCP client command and wait for return
534 if not shellcmd(dhcp_command): return
535 else:
536 self.logger.debug("Killing DHCP manually...")
537 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
538 except OSError:
539 print "failed to kill DHCP client"
540 self.logger.debug("Kill off any existing WPA supplicants running...")
541 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
542 self.logger.debug("Killing existing WPA supplicant...")
543 try:
544 if not self.confFile.get_opt('WPA.kill_command') != '':
545 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
546 if not shellcmd(wpa_command): return
547 else:
548 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
549 except OSError:
550 print "failed to kill WPA supplicant"
551 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
552 self.logger.debug("Let's clear out the wireless stuff")
553 self.logger.debug("Now take the interface down")
554 # taking down the interface too quickly can crash my system, so pause a moment
555 sleep(1)
556 self.if_change('down')
557 self.logger.debug("Since it may be brought back up by the next scan, lets unset its IP")
558 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
559 # Let's run the disconnection postscript
560 if self.profile['dis_postscript'].strip() != '':
561 self.logger.debug("executing disconnection postscript: %s" % ( self.profile['dis_postscript'], ))
562 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
563 self.state = False
564 self.logger.debug("Disconnect complete.")
565 # Begin scanning again
566 try:
567 self.commQueue.put("scan")
568 except Queue.Full:
569 pass
571 # Returns the current IP, if any, by calling ifconfig.
573 #Parameters:
575 # nothing
577 #Returns:
579 # string or None -- the IP address or None (if no there is no current connection)
580 def get_current_ip( self ):
581 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
582 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
583 # Be careful to the language (inet adr: in French for example)
585 # Hi Brian
587 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
588 # There the string in ifconfig is inet Adresse for the IP which isn't
589 # found by the current get_current_ip function in wifi-radar. I changed
590 # the according line (#289; gentoo, v1.9.6-r1) to
591 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
592 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
594 # I'd be happy if you could incorporate this small change because as now
595 # I've got to change the file every time it is updated.
597 # Best wishes
599 # Simon
600 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
601 line = ifconfig_info.read()
602 if ip_re.search( line ):
603 return ip_re.search( line ).group(1)
604 return None
606 # Returns the current ESSID, if any, by calling iwconfig.
608 #Parameters:
610 # nothing
612 #Returns:
614 # string or None -- the ESSID or None (if no there is no current association)
615 def get_current_essid( self ):
616 """Returns the current ESSID if any by calling iwconfig"""
617 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
618 # Be careful to the language (inet adr: in French for example)
619 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
620 line = iwconfig_info.read()
621 if essid_re.search( line ):
622 return essid_re.search( line ).group(2)
623 return None
625 # Returns the current BSSID, if any, by calling iwconfig.
627 #Parameters:
629 # nothing
631 #Returns:
633 # string or None -- the BSSID or None (if no there is no current association)
634 def get_current_bssid( self ):
635 """Returns the current BSSID if any by calling iwconfig"""
636 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
637 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
638 line = iwconfig_info.read()
639 if bssid_re.search( line ):
640 return bssid_re.search( line ).group(2)
641 return None
645 # The main user interface window for WiFi Radar. This class also is the control
646 # center for most of the rest of the operations.
647 class radar_window:
648 # Create a new radar_window.
650 #Parameters:
652 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
654 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
656 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
658 # 'logger' -- Logger - Python's logging facility
660 #Returns:
662 # radar_window instance
663 def __init__( self, confFile, apQueue, commQueue, logger ):
664 global signal_xpm_none
665 global signal_xpm_low
666 global signal_xpm_barely
667 global signal_xpm_ok
668 global signal_xpm_best
669 global known_profile_icon
670 global unknown_profile_icon
671 global wifi_radar_icon
673 self.confFile = confFile
674 self.apQueue = apQueue
675 self.commandQueue = commQueue
676 self.logger = logger
677 self.access_points = {}
678 self.connection = None
680 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
681 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
682 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
683 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
684 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
685 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
686 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
687 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
688 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
689 self.window.set_icon( icon )
690 self.window.set_border_width( 10 )
691 self.window.set_size_request( 550, 300 )
692 self.window.set_title( "WiFi Radar" )
693 self.window.connect( 'delete_event', self.delete_event )
694 self.window.connect( 'destroy', self.destroy )
695 # let's create all our widgets
696 self.current_network = gtk.Label()
697 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
698 self.current_network.show()
699 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
700 self.close_button.show()
701 self.close_button.connect( 'clicked', self.delete_event, None )
702 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
703 self.about_button.show()
704 self.about_button.connect( 'clicked', self.show_about_info, None )
705 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
706 self.preferences_button.show()
707 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
708 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
709 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
710 self.plist = gtk.TreeView( self.pstore )
711 # The icons column, known and encryption
712 self.pix_cell = gtk.CellRendererPixbuf()
713 self.wep_cell = gtk.CellRendererPixbuf()
714 self.icons_cell = gtk.CellRendererText()
715 self.icons_col = gtk.TreeViewColumn()
716 self.icons_col.pack_start( self.pix_cell, False )
717 self.icons_col.pack_start( self.wep_cell, False )
718 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
719 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
720 self.plist.append_column( self.icons_col )
721 # The AP column
722 self.ap_cell = gtk.CellRendererText()
723 self.ap_col = gtk.TreeViewColumn( "Access Point" )
724 self.ap_col.pack_start( self.ap_cell, True )
725 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
726 self.plist.append_column( self.ap_col )
727 # The signal column
728 self.sig_cell = gtk.CellRendererPixbuf()
729 self.signal_col = gtk.TreeViewColumn( "Signal" )
730 self.signal_col.pack_start( self.sig_cell, True )
731 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
732 self.plist.append_column( self.signal_col )
733 # The mode column
734 self.mode_cell = gtk.CellRendererText()
735 self.mode_col = gtk.TreeViewColumn( "Mode" )
736 self.mode_col.pack_start( self.mode_cell, True )
737 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
738 self.plist.append_column( self.mode_col )
739 # The protocol column
740 self.prot_cell = gtk.CellRendererText()
741 self.protocol_col = gtk.TreeViewColumn( "802.11" )
742 self.protocol_col.pack_start( self.prot_cell, True )
743 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
744 self.plist.append_column( self.protocol_col )
745 # The channel column
746 self.channel_cell = gtk.CellRendererText()
747 self.channel_col = gtk.TreeViewColumn( "Channel" )
748 self.channel_col.pack_start( self.channel_cell, True )
749 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
750 self.plist.append_column( self.channel_col )
751 # DnD Ordering
752 self.plist.set_reorderable( True )
753 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
754 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
755 # enable/disable buttons based on the selected network
756 self.selected_network = self.plist.get_selection()
757 self.selected_network.connect( 'changed', self.on_network_selection, None )
758 # the list scroll bar
759 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
760 sb.show()
761 self.plist.show()
762 # Add New button
763 self.new_button = gtk.Button( "_New" )
764 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
765 self.new_button.show()
766 # Add Configure button
767 self.edit_button = gtk.Button( "C_onfigure" )
768 self.edit_button.connect( 'clicked', self.edit_profile, None )
769 self.edit_button.show()
770 self.edit_button.set_sensitive(False)
771 # Add Delete button
772 self.delete_button = gtk.Button( "_Delete" )
773 self.delete_button.connect( 'clicked', self.delete_profile, None )
774 self.delete_button.show()
775 self.delete_button.set_sensitive(False)
776 # Add Connect button
777 self.connect_button = gtk.Button( "Co_nnect" )
778 self.connect_button.connect( 'clicked', self.connect_profile, None )
779 # Add Disconnect button
780 self.disconnect_button = gtk.Button( "D_isconnect" )
781 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
782 # lets add our widgets
783 rows = gtk.VBox( False, 3 )
784 net_list = gtk.HBox( False, 0 )
785 listcols = gtk.HBox( False, 0 )
786 prows = gtk.VBox( False, 0 )
787 # lets start packing
788 # the network list
789 net_list.pack_start( self.plist, True, True, 0 )
790 net_list.pack_start( sb, False, False, 0 )
791 # the rows level
792 rows.pack_start( net_list , True, True, 0 )
793 rows.pack_start( self.current_network, False, True, 0 )
794 # the list columns
795 listcols.pack_start( rows, True, True, 0 )
796 listcols.pack_start( prows, False, False, 5 )
797 # the list buttons
798 prows.pack_start( self.new_button, False, False, 2 )
799 prows.pack_start( self.edit_button, False, False, 2 )
800 prows.pack_start( self.delete_button, False, False, 2 )
801 prows.pack_end( self.connect_button, False, False, 2 )
802 prows.pack_end( self.disconnect_button, False, False, 2 )
804 self.window.action_area.pack_start( self.about_button )
805 self.window.action_area.pack_start( self.preferences_button )
806 self.window.action_area.pack_start( self.close_button )
808 rows.show()
809 prows.show()
810 listcols.show()
811 self.window.vbox.add( listcols )
812 self.window.vbox.set_spacing( 3 )
813 self.window.show_all()
815 # Now, immediately hide these two. The proper one will be
816 # displayed later, based on interface state. -BEF-
817 self.disconnect_button.hide()
818 self.connect_button.hide()
819 self.connect_button.set_sensitive(False)
821 # set up connection manager for later use
822 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
823 # set up status window for later use
824 self.status_window = StatusWindow( self )
825 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
827 # Add our known profiles in order
828 for ap in self.confFile.auto_profile_order:
829 ap = ap.strip()
830 self.access_points[ ap ] = self.confFile.get_profile( ap )
831 wep = None
832 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
833 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'] ] )
834 # This is the first run (or, at least, no config file was present), so pop up the preferences window
835 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
836 self.confFile.remove_option('DEFAULT', 'new_file')
837 self.edit_preferences(self.preferences_button)
839 # Begin running radar_window in Gtk event loop.
841 #Parameters:
843 # nothing
845 #Returns:
847 # nothing
848 def main( self ):
849 gtk.main()
851 # Quit application.
853 #Parameters:
855 # 'widget' -- gtk.Widget - The widget sending the event.
857 #Returns:
859 # nothing
860 def destroy( self, widget = None):
861 if self.status_window:
862 self.status_window.destroy()
863 gtk.main_quit()
865 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
867 #Parameters:
869 # 'widget' -- gtk.Widget - The widget sending the event.
871 # 'data' -- tuple - list of arbitrary arguments (not used)
873 #Returns:
875 # boolean -- always return False (i.e. do not propigate the signal which called)
876 def delete_event( self, widget, data = None ):
877 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
878 try:
879 self.commandQueue.put("exit", True)
880 except Queue.Full:
881 pass
882 self.destroy()
883 return False
885 # Updates the on-screen profiles list.
887 #Parameters:
889 # nothing
891 #Returns:
893 # boolean -- always return True
894 def update_list( self, profile ):
895 # Indicate to PyGtk that only one Gtk thread should run here
896 gtk.gdk.threads_enter()
897 # update the current ip and essid
898 # set the state of connect/disconnect buttons based on whether we have an IP address
899 if self.connection:
900 if self.connection.state:
901 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() ) )
902 self.connect_button.hide()
903 self.disconnect_button.show()
904 else:
905 self.current_network.set_text( "Not Connected." )
906 self.disconnect_button.hide()
907 self.connect_button.show()
909 while True:
910 # Get profiles scanned by iwlist
911 try:
912 profile = self.apQueue.get_nowait()
913 except Queue.Empty:
914 break
915 else:
916 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
917 wep = None
918 if prow_iter != None:
919 # the AP is in the list of APs on the screen
920 apname = make_section_name(profile['essid'], profile['bssid'])
921 if self.access_points.has_key(apname):
922 # This AP has been configured and is/should be stored in the config file
923 profile['known'] = self.access_points[apname]['known']
924 self.access_points[apname]['available'] = profile['available']
925 self.access_points[apname]['encrypted'] = profile['encrypted']
926 self.access_points[apname]['signal'] = profile['signal']
927 self.access_points[apname]['mode'] = profile['mode']
928 self.access_points[apname]['protocol'] = profile['protocol']
929 self.access_points[apname]['channel'] = profile['channel']
930 # Set the 'known' values; False is default, overridden to True by self.access_points
931 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
932 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
933 self.pstore.set_value(prow_iter, 3, profile['available'])
934 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
935 self.pstore.set_value(prow_iter, 4, wep)
936 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
937 self.pstore.set_value(prow_iter, 6, profile['mode'])
938 self.pstore.set_value(prow_iter, 7, profile['protocol'])
939 self.pstore.set_value(prow_iter, 8, profile['channel'])
940 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
941 #for val in self.pstore[prow_iter]:
942 #print val,
943 else:
944 # the AP is not in the list of APs on the screen
945 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'] ] )
946 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
947 # Allow other Gtk threads to run
948 gtk.gdk.threads_leave()
949 #print "update_plist_items: Empty apQueue"
950 return True
952 # Return the proper icon for a value of known.
954 #Parameters:
956 # 'known' -- boolean - Whether the AP is known (i.e. configured)
958 #Returns:
960 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
961 def pixbuf_from_known( self, known ):
962 """ return the proper icon for value of known """
963 if known:
964 return self.known_profile_icon
965 else:
966 return self.unknown_profile_icon
968 # Return an icon indicating the signal level.
970 #Parameters:
972 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
974 #Returns:
976 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
977 def pixbuf_from_signal( self, signal ):
978 signal = int( signal )
979 # shift signal up by 80 to convert dBm scale to arbitrary scale
980 if signal < 0: signal = signal + 80
981 #print "signal level:", signal
982 if signal < 3:
983 return self.signal_none_pb
984 elif signal < 12:
985 return self.signal_low_pb
986 elif signal < 20:
987 return self.signal_barely_pb
988 elif signal < 35:
989 return self.signal_ok_pb
990 elif signal >= 35:
991 return self.signal_best_pb
992 else:
993 return None
995 # Return row which holds specified ESSID and BSSID.
997 #Parameters:
999 # 'essid' -- string - ESSID to match
1001 # 'bssid' -- string - BSSID to match
1003 #Returns:
1005 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1006 def get_row_by_ap( self, essid, bssid ):
1007 for row in self.pstore:
1008 if ( row[0] == essid + "\n" + bssid ):
1009 #print "matched:", row.iter, essid, bssid
1010 return row.iter
1011 return None
1013 # Enable/disable buttons based on the selected network.
1015 #Parameters:
1017 # 'widget' -- gtk.Widget - The widget sending the event.
1019 # 'data' -- tuple - list of arbitrary arguments (not used)
1021 #Returns:
1023 # nothing
1024 def on_network_selection( self, widget, data = None ):
1025 ( store, selected_iter ) = self.selected_network.get_selected()
1026 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1027 # if no networks are selected, disable all buttons except New
1028 # (this occurs after a drag-and-drop)
1029 if selected_iter == None:
1030 self.edit_button.set_sensitive(False)
1031 self.delete_button.set_sensitive(False)
1032 self.connect_button.set_sensitive(False)
1033 return
1034 # enable/disable buttons
1035 self.connect_button.set_sensitive(True)
1036 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1037 self.edit_button.set_sensitive(True)
1038 self.delete_button.set_sensitive(True)
1039 else:
1040 self.edit_button.set_sensitive(True)
1041 self.delete_button.set_sensitive(False)
1043 # Init and run the about dialog
1045 #Parameters:
1047 # 'widget' -- gtk.Widget - The widget sending the event.
1049 # 'data' -- tuple - list of arbitrary arguments (not used)
1051 #Returns:
1053 # nothing
1054 def show_about_info( self, widget, data=None ):
1055 about = about_dialog()
1056 about.run()
1057 about.destroy()
1059 # Init and run the preferences dialog
1061 #Parameters:
1063 # 'widget' -- gtk.Widget - The widget sending the event.
1065 # 'data' -- tuple - list of arbitrary arguments (not used)
1067 #Returns:
1069 # nothing
1070 def edit_preferences( self, widget, data=None ):
1071 # get raw strings from config file
1072 self.confFile.raw = True
1073 prefs = preferences_dialog( self, self.confFile )
1074 response = prefs.run()
1075 prefs.destroy()
1076 if response == int(gtk.RESPONSE_ACCEPT):
1077 prefs.save()
1078 # get cooked strings from config file
1079 self.confFile.raw = False
1081 # Respond to a request to create a new AP profile
1083 #Parameters:
1085 # 'widget' -- gtk.Widget - The widget sending the event.
1087 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1089 # 'data' -- tuple - list of arbitrary arguments (not used)
1091 #Returns:
1093 # boolean -- True if a profile was created and False if profile creation was canceled.
1094 def create_new_profile( self, widget, profile, data=None ):
1095 profile_editor = profile_dialog( self, profile )
1096 try:
1097 profile = profile_editor.run()
1098 except ValueError:
1099 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1100 del error_dlg
1101 return False
1102 finally:
1103 profile_editor.destroy()
1104 if profile:
1105 apname = make_section_name( profile['essid'], profile['bssid'] )
1106 # Check that the ap does not exist already
1107 if apname in self.confFile.profiles():
1108 error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
1109 del error_dlg
1110 # try again
1111 self.access_points[ apname ] = profile
1112 self.confFile.set_section( apname, profile )
1113 # if it is not in the auto_profile_order add it
1114 if apname not in self.confFile.auto_profile_order:
1115 self.confFile.auto_profile_order.append(apname)
1116 # add to the store
1117 wep = None
1118 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1119 try:
1120 self.confFile.write()
1121 except IOError, (error_number, error_str):
1122 if error_number == errno.ENOENT:
1123 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1124 del error_dlg
1125 else:
1126 raise IOError(error_number, error_str)
1127 # Add AP to the list displayed to user
1128 try:
1129 self.apQueue.put_nowait( self.access_points[ apname ] )
1130 except Queue.Full:
1131 pass
1132 return True
1133 else:
1134 # Did not create new profile
1135 return False
1137 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1138 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1140 #Parameters:
1142 # 'widget' -- gtk.Widget - The widget sending the event.
1144 # 'data' -- tuple - list of arbitrary arguments (not used)
1146 #Returns:
1148 # nothing
1149 def edit_profile( self, widget, data=None ):
1150 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1151 if not selected_iter: return
1152 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1153 apname = make_section_name( row_start[0], row_start[1] )
1154 profile = self.confFile.get_profile( apname )
1155 if profile:
1156 profile_editor = profile_dialog( self, profile )
1157 try:
1158 profile = profile_editor.run()
1159 except ValueError:
1160 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1161 del error_dlg
1162 return False
1163 finally:
1164 profile_editor.destroy()
1165 if profile:
1166 apname = make_section_name( profile['essid'], profile['bssid'] )
1167 self.access_points[ apname ] = profile
1168 self.confFile.set_section( apname, profile )
1169 try:
1170 self.confFile.write()
1171 except IOError, (error_number, error_str):
1172 if error_number == errno.ENOENT:
1173 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1174 del error_dlg
1175 else:
1176 raise IOError(error_number, error_str)
1177 else:
1178 profile = get_new_profile()
1179 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1180 self.create_new_profile( widget, profile, data )
1182 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1184 #Parameters:
1186 # 'widget' -- gtk.Widget - The widget sending the event.
1188 # 'data' -- tuple - list of arbitrary arguments (not used)
1190 #Returns:
1192 # nothing
1193 def delete_profile( self, widget, data=None ):
1194 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1195 if not selected_iter: return
1196 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1197 known = store.get_value( selected_iter, 1 )
1198 if not known: return
1199 dlg = gtk.MessageDialog(
1200 self.window,
1201 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1202 gtk.MESSAGE_QUESTION,
1203 gtk.BUTTONS_YES_NO,
1204 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1205 res = dlg.run()
1206 dlg.destroy()
1207 del dlg
1208 if res == gtk.RESPONSE_NO: return
1209 # Remove it
1210 apname = make_section_name( essid, bssid )
1211 del self.access_points[ apname ]
1212 self.confFile.remove_section( apname )
1213 self.logger.debug(apname)
1214 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1215 self.pstore.remove( selected_iter )
1216 # Let's save our current state
1217 self.update_auto_profile_order()
1218 try:
1219 self.confFile.write()
1220 except IOError, (error_number, error_str):
1221 if error_number == errno.ENOENT:
1222 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1223 del error_dlg
1224 else:
1225 raise IOError(error_number, error_str)
1227 # Respond to a request to connect to an AP.
1229 #Parameters:
1231 # 'widget' -- gtk.Widget - The widget sending the event.
1233 # 'profile' -- dictionary - The AP profile to which to connect.
1235 # 'data' -- tuple - list of arbitrary arguments (not used)
1237 #Returns:
1239 # nothing
1240 def connect_profile( self, widget, profile, data=None ):
1241 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1242 if not selected_iter: return
1243 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1244 known = store.get_value( selected_iter, 2 )
1245 if not known:
1246 if data != 'noconnect':
1247 dlg = gtk.MessageDialog(
1248 self.window,
1249 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1250 gtk.MESSAGE_QUESTION,
1251 gtk.BUTTONS_YES_NO,
1252 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1253 res = dlg.run()
1254 dlg.destroy()
1255 del dlg
1256 if res == gtk.RESPONSE_NO: return
1257 profile = get_new_profile()
1258 profile['essid'] = essid
1259 profile['bssid'] = bssid
1260 if not self.create_new_profile( widget, profile, data ):
1261 return
1262 apname = make_section_name( essid, bssid )
1263 self.connection.connect_to_network(self.access_points[apname], self.status_window)
1265 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1267 #Parameters:
1269 # 'widget' -- gtk.Widget - The widget sending the event.
1271 # 'data' -- tuple - list of arbitrary arguments (not used)
1273 #Returns:
1275 # nothing
1276 def disconnect_profile( self, widget, data=None ):
1277 if data == "cancel":
1278 self.status_window.update_message("Canceling connection...")
1279 if sys.modules.has_key("gtk"):
1280 while gtk.events_pending():
1281 gtk.main_iteration(False)
1282 sleep(1)
1283 self.connection.disconnect_interface()
1285 # Update the config file auto profile order from the on-screen order
1287 #Parameters:
1289 # 'widget' -- gtk.Widget - The widget sending the event.
1291 # 'data' -- tuple - list of arbitrary arguments (not used)
1293 # 'data2' -- tuple - list of arbitrary arguments (not used)
1295 #Returns:
1297 # nothing
1298 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1299 # recreate the auto_profile_order
1300 auto_profile_order = []
1301 piter = self.pstore.get_iter_first()
1302 while piter:
1303 # only if it's known
1304 if self.pstore.get_value( piter, 2 ) == True:
1305 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1306 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1307 piter = self.pstore.iter_next( piter )
1308 self.confFile.auto_profile_order = auto_profile_order
1309 try:
1310 self.confFile.write()
1311 except IOError, (error_number, error_str):
1312 if error_number == errno.ENOENT:
1313 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1314 del error_dlg
1315 else:
1316 raise IOError(error_number, error_str)
1319 # Button to allow user to choose a file and put value into specified gtk.Entry
1320 class file_browse_button(gtk.Button):
1321 # Create a button to simulate a File/Open
1323 #Parameters:
1325 # 'parent' -- gtk.Object -- Usually, the calling window.
1327 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1329 #Returns:
1331 # file_browse_button instance
1332 def __init__( self, parent, entry ):
1333 self.parent_window = parent
1334 self.entry = entry
1335 gtk.Button.__init__(self, "Browse", None)
1336 #self.
1337 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)
1338 self.connect("clicked", self.browse_files)
1340 # Show filechooser dialog and get user selection
1342 #Parameters:
1344 # 'widget' -- gtk.Widget -- The widget sending the event.
1346 #Returns:
1348 # nothing
1350 #NOTES:
1352 # updates entry value
1354 def browse_files( self, widget ):
1355 self.browser_dialog.set_filename(self.entry.get_text())
1356 self.browser_dialog.run()
1357 self.entry.set_text(self.browser_dialog.get_filename())
1358 self.browser_dialog.destroy()
1361 # Simple dialog to report an error to the user.
1362 class ErrorDialog:
1363 # Create a new ErrorDialog.
1365 #Parameters:
1367 # 'parent' -- gtk.Object - Usually, the calling window.
1369 # 'message' -- string - The message to display to the user.
1371 #Returns:
1373 # ErrorDialog instance
1374 def __init__( self, parent, message ):
1375 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1376 dialog.run()
1377 dialog.destroy()
1378 del dialog
1381 # The preferences dialog. Edits non-profile sections of the config file.
1382 class preferences_dialog:
1383 # Create a new preferences_dialog.
1385 #Parameters:
1387 # 'parent' -- gtk.Object - Usually, the calling window.
1389 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1391 #Returns:
1393 # preferences_dialog instance
1394 def __init__( self, parent, confFile ):
1395 global wifi_radar_icon
1396 self.parent = parent
1397 self.confFile = confFile
1398 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1399 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1400 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1401 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1402 self.dialog.set_icon( icon )
1403 self.dialog.set_resizable( True )
1404 self.dialog.set_transient_for( self.parent.window )
1405 self.tooltips = gtk.Tooltips()
1407 # set up preferences widgets
1409 # build everything in a tabbed notebook
1410 self.prefs_notebook = gtk.Notebook()
1412 ### General tab
1413 self.general_page = gtk.VBox()
1414 # auto detect wireless device
1415 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1417 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1419 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1420 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1421 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1422 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1424 # network interface selecter
1425 self.w_interface = gtk.combo_box_entry_new_text()
1426 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1427 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1428 for device in wireless_devices:
1429 if device != self.confFile.get_opt('DEFAULT.interface'):
1430 self.w_interface.append_text(device)
1431 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1432 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1433 self.w_interface.set_active(0)
1434 self.w_interface_label = gtk.Label("Wireless device")
1435 self.w_hbox1 = gtk.HBox(False, 0)
1436 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1437 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1438 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1439 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1441 # scan timeout (spin button of integers from 1 to 100)
1442 #self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1443 #self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1444 #self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1445 #self.w_scan_timeout.set_numeric(True)
1446 #self.w_scan_timeout.set_snap_to_ticks(True)
1447 #self.w_scan_timeout.set_wrap(False)
1448 #self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1449 #self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1450 #self.w_hbox2 = gtk.HBox(False, 0)
1451 #self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1452 #self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1453 #self.general_page.pack_start(self.w_hbox2, False, False, 5)
1455 # speak up
1456 self.w_speak_up = gtk.CheckButton("Use speak-up")
1457 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1458 self.w_speak_up.connect("toggled", self.toggle_speak)
1459 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1460 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1462 # speak up command
1463 self.w_speak_cmd = gtk.Entry()
1464 self.w_speak_cmd.set_width_chars(16)
1465 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1466 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1467 self.w_speak_cmd_label = gtk.Label("Speak Command")
1468 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1469 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1470 self.w_hbox3 = gtk.HBox(False, 0)
1471 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1472 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1473 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1474 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1475 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1477 # commit required
1478 self.w_commit_required = gtk.CheckButton("Commit required")
1479 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1480 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1481 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1483 # ifup required
1484 self.w_ifup_required = gtk.CheckButton("Ifup required")
1485 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1486 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1487 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1489 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1490 ### End of General tab
1492 ### Advanced tab
1493 # table to use for layout of following command configurations
1494 self.cmds_table = gtk.Table()
1496 # ifconfig command
1497 self.ifconfig_cmd = gtk.Entry()
1498 self.ifconfig_cmd.set_width_chars(32)
1499 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1500 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1501 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1502 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1503 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1504 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1505 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1506 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1508 # iwconfig command
1509 self.iwconfig_cmd = gtk.Entry()
1510 self.iwconfig_cmd.set_width_chars(32)
1511 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1512 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1513 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1514 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1515 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1516 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1517 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1518 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1520 # iwlist command
1521 self.iwlist_cmd = gtk.Entry()
1522 self.iwlist_cmd.set_width_chars(32)
1523 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1524 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1525 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1526 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1527 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1528 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1529 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1530 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1532 # route command
1533 self.route_cmd = gtk.Entry()
1534 self.route_cmd.set_width_chars(32)
1535 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1536 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1537 self.route_cmd_label = gtk.Label("Network route configure command")
1538 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1539 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1540 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1541 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1542 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1544 # log file
1545 self.logfile_entry = gtk.Entry()
1546 self.logfile_entry.set_width_chars(32)
1547 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1548 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1549 self.logfile_label = gtk.Label("Log file")
1550 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1551 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1552 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, True, False, 5, 0)
1553 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, True, False, 0, 0)
1554 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, False, False, 0, 0)
1556 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1557 ### End of Advanced tab
1559 ### DHCP tab
1560 # table to use for layout of DHCP prefs
1561 self.dhcp_table = gtk.Table()
1563 self.dhcp_cmd = gtk.Entry()
1564 self.dhcp_cmd.set_width_chars(32)
1565 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1566 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1567 self.dhcp_cmd_label = gtk.Label("Command")
1568 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1569 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1570 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1571 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1573 self.dhcp_args = gtk.Entry()
1574 self.dhcp_args.set_width_chars(32)
1575 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1576 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1577 self.dhcp_args_label = gtk.Label("Arguments")
1578 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1579 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1581 self.dhcp_kill_args = gtk.Entry()
1582 self.dhcp_kill_args.set_width_chars(32)
1583 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1584 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1585 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1586 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1587 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1589 self.dhcp_timeout = gtk.Entry()
1590 self.dhcp_timeout.set_width_chars(32)
1591 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1592 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1593 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1594 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1595 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1597 self.dhcp_pidfile = gtk.Entry()
1598 self.dhcp_pidfile.set_width_chars(32)
1599 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1600 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1601 self.dhcp_pidfile_label = gtk.Label("PID file")
1602 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1603 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1605 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1606 ### End of DHCP tab
1608 ### WPA tab
1609 # table to use for layout of DHCP prefs
1610 self.wpa_table = gtk.Table()
1612 self.wpa_cmd = gtk.Entry()
1613 self.wpa_cmd.set_width_chars(32)
1614 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1615 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1616 self.wpa_cmd_label = gtk.Label("Command")
1617 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1618 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1619 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1620 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1622 self.wpa_args = gtk.Entry()
1623 self.wpa_args.set_width_chars(32)
1624 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1625 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1626 self.wpa_args_label = gtk.Label("Arguments")
1627 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1628 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1630 self.wpa_kill_args = gtk.Entry()
1631 self.wpa_kill_args.set_width_chars(32)
1632 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1633 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1634 self.wpa_kill_args_label = gtk.Label("Kill command")
1635 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1636 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1638 self.wpa_config = gtk.Entry()
1639 self.wpa_config.set_width_chars(32)
1640 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1641 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1642 self.wpa_config_label = gtk.Label("Configuration file")
1643 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1644 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1646 self.wpa_driver = gtk.Entry()
1647 self.wpa_driver.set_width_chars(32)
1648 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1649 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1650 self.wpa_driver_label = gtk.Label("Driver")
1651 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1652 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1654 self.wpa_pidfile = gtk.Entry()
1655 self.wpa_pidfile.set_width_chars(32)
1656 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1657 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1658 self.wpa_pidfile_label = gtk.Label("PID file")
1659 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1660 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1662 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1663 ### End of WPA tab
1665 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1667 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1669 #Parameters:
1671 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1673 # 'data' -- tuple - list of arbitrary arguments (not used)
1675 #Returns:
1677 # nothing
1678 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1679 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1681 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1683 #Parameters:
1685 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1687 # 'data' -- tuple - list of arbitrary arguments (not used)
1689 #Returns:
1691 # nothing
1692 def toggle_speak(self, speak_toggle, data=None):
1693 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1694 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1696 # Display preferences dialog and operate until canceled or okayed.
1698 #Parameters:
1700 # nothing
1702 #Returns:
1704 # integer -- gtk response ID
1705 def run(self):
1706 self.dialog.show_all()
1707 return self.dialog.run()
1709 # Write updated values to config file.
1711 #Parameters:
1713 # nothing
1715 #Returns:
1717 # nothing
1718 def save(self):
1719 if self.w_auto_detect.get_active():
1720 set_network_device("auto_detect")
1721 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1722 else:
1723 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1724 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1725 self.confFile.set_opt('DEFAULT.interface', interface)
1726 set_network_device(interface)
1727 #self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1728 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1729 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1730 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1731 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1732 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1733 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1734 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1735 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1736 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1737 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1738 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1739 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1740 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1741 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1742 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1743 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1744 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1745 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1746 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1747 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1748 try:
1749 self.confFile.write()
1750 except IOError, (error_number, error_str):
1751 if error_number == errno.ENOENT:
1752 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1753 del error_dlg
1754 else:
1755 raise IOError(error_number, error_str)
1757 # Remove preferences window.
1759 #Parameters:
1761 # nothing
1763 #Returns:
1765 # nothing
1766 def destroy(self):
1767 self.dialog.destroy()
1768 del self.dialog
1771 # Edit and return an AP profile.
1772 class profile_dialog:
1773 # Create a new profile_dialog.
1775 #Parameters:
1777 # 'parent' -- gtk.Object - Usually, the calling window.
1779 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1781 #Returns:
1783 # profile_dialog instance
1784 def __init__( self, parent, profile ):
1785 global wifi_radar_icon
1787 # Labels
1788 WIFI_SET_LABEL = "WiFi Options"
1789 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
1790 USE_IP_LABEL = "Manual network configuration"
1791 USE_WPA_LABEL = "Use WPA"
1792 NO_WPA_LABEL = "No WPA"
1793 CON_PP_LABEL = "Connection Commands"
1794 DIS_PP_LABEL = "Disconnection Commands"
1796 self.parent = parent
1797 self.profile = profile.copy()
1798 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1799 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1800 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1801 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1802 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1803 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1804 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1805 self.dialog.set_icon( icon )
1806 self.dialog.set_resizable( False )
1807 self.dialog.set_transient_for( self.parent.window )
1808 #self.dialog.set_size_request( 400, 400 )
1809 #################
1810 self.tooltips = gtk.Tooltips()
1812 general_table = gtk.Table()
1813 general_table.set_row_spacings(3)
1814 general_table.set_col_spacings(3)
1815 # The essid labels
1816 general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
1817 # The essid textboxes
1818 self.essid_entry = gtk.Entry(32)
1819 self.essid_entry.set_text(self.profile['essid'])
1820 general_table.attach(self.essid_entry, 1, 2, 0, 1)
1821 # Add the essid table to the dialog
1822 self.dialog.vbox.pack_start(general_table, True, True, 5)
1823 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
1825 # The bssid labels
1826 general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
1827 # The bssid textboxes
1828 self.bssid_entry = gtk.Entry(32)
1829 self.bssid_entry.set_text(self.profile['bssid'])
1830 self.bssid_entry.set_sensitive(not self.profile['roaming'])
1831 general_table.attach(self.bssid_entry, 1, 2, 1, 2)
1832 # Add the bssid table to the dialog
1833 self.dialog.vbox.pack_start(general_table, True, True, 5)
1834 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
1835 # Add the roaming checkbox
1836 self.roaming_cb = gtk.CheckButton('Roaming')
1837 self.roaming_cb.set_active(self.profile['roaming'])
1838 general_table.attach(self.roaming_cb, 1, 2, 1, 2)
1839 self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
1840 # create the WiFi expander
1841 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1842 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1843 wifi_table = gtk.Table( 4, 2, False )
1844 wifi_table.set_row_spacings( 3 )
1845 wifi_table.set_col_spacings( 3 )
1846 # The WiFi labels
1847 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1848 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1849 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1850 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1851 # The WiFi text boxes
1852 self.mode_combo = gtk.combo_box_new_text()
1853 for mode in self.WIFI_MODES:
1854 self.mode_combo.append_text( mode )
1855 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1856 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1857 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
1858 self.channel_combo = gtk.combo_box_new_text()
1859 for channel in self.WIFI_CHANNELS:
1860 self.channel_combo.append_text( channel )
1861 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1862 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1863 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
1865 self.key_entry = gtk.Entry( 64 )
1866 self.key_entry.set_text( self.profile['key'] )
1867 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1868 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
1870 self.security_combo = gtk.combo_box_new_text()
1871 for security in self.WIFI_SECURITY:
1872 self.security_combo.append_text( security )
1873 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1874 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1875 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
1876 # Add the wifi table to the expander
1877 self.wifi_expander.add( wifi_table )
1878 # Add the expander to the dialog
1879 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1881 # create the wpa expander
1882 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1883 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1884 wpa_table = gtk.Table( 1, 2, False )
1885 wpa_table.set_row_spacings( 3 )
1886 wpa_table.set_col_spacings( 3 )
1887 # The labels
1888 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1889 # The text boxes
1890 self.wpa_driver_entry = gtk.Entry()
1891 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1892 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1893 # Add the wpa table to the expander
1894 self.wpa_expander.add( wpa_table )
1895 # Add the expander to the dialog
1896 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1898 # create the dhcp expander
1899 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1900 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1901 ip_table = gtk.Table( 6, 2, False )
1902 ip_table.set_row_spacings( 3 )
1903 ip_table.set_col_spacings( 3 )
1904 # The IP labels
1905 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1906 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1907 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1908 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1909 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1910 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1911 # The IP text boxes
1912 self.ip_entry = gtk.Entry( 15 )
1913 self.ip_entry.set_text( self.profile['ip'] )
1914 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1915 self.netmask_entry = gtk.Entry( 15 )
1916 self.netmask_entry.set_text( self.profile['netmask'] )
1917 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1918 self.gw_entry = gtk.Entry( 15 )
1919 self.gw_entry.set_text( self.profile['gateway'] )
1920 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1921 self.domain_entry = gtk.Entry( 32 )
1922 self.domain_entry.set_text( self.profile['domain'] )
1923 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1924 self.dns1_entry = gtk.Entry( 15 )
1925 self.dns1_entry.set_text( self.profile['dns1'] )
1926 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1927 self.dns2_entry = gtk.Entry( 15 )
1928 self.dns2_entry.set_text( self.profile['dns2'] )
1929 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1930 # Add the ip table to the expander
1931 self.dhcp_expander.add( ip_table )
1932 # Add the expander to the dialog
1933 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1935 # create the connection-building postpre expander
1936 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1937 con_pp_table = gtk.Table( 2, 2, False )
1938 con_pp_table.set_row_spacings( 3 )
1939 con_pp_table.set_col_spacings( 3 )
1940 # The labels
1941 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1942 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1943 # The text boxes
1944 self.con_prescript_entry = gtk.Entry()
1945 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1946 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1947 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
1948 self.con_postscript_entry = gtk.Entry()
1949 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1950 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1951 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
1952 # Add the pp table to the expander
1953 self.con_pp_expander.add( con_pp_table )
1954 # Add the expander to the dialog
1955 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1957 # create the disconnection postpre expander
1958 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1959 dis_pp_table = gtk.Table( 2, 2, False )
1960 dis_pp_table.set_row_spacings( 3 )
1961 dis_pp_table.set_col_spacings( 3 )
1962 # The labels
1963 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1964 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1965 # The text boxes
1966 self.dis_prescript_entry = gtk.Entry()
1967 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1968 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1969 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
1970 self.dis_postscript_entry = gtk.Entry()
1971 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1972 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1973 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
1974 # Add the pp table to the expander
1975 self.dis_pp_expander.add( dis_pp_table )
1976 # Add the expander to the dialog
1977 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1979 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1981 #Parameters:
1983 # nothing
1985 #Returns:
1987 # dictionary or None -- a profile, or None on cancel
1989 #NOTES:
1991 # Raises ValueError if an attempt is made to save an ESSID with no name.
1992 def run( self ):
1993 self.dialog.show_all()
1994 if self.dialog.run():
1995 if self.essid_entry.get_text().strip() == "":
1996 raise ValueError
1997 self.profile['known'] = True
1998 self.profile['essid'] = self.essid_entry.get_text().strip()
1999 self.profile['bssid'] = self.bssid_entry.get_text().strip()
2000 self.profile['roaming'] = self.roaming_cb.get_active()
2001 self.profile['key'] = self.key_entry.get_text().strip()
2002 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
2003 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
2004 self.profile['encrypted'] = ( self.profile['security'] != '' )
2005 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
2006 self.profile['protocol'] = 'g'
2007 self.profile['available'] = ( self.profile['signal'] > 0 )
2008 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
2009 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
2010 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
2011 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
2012 # wpa
2013 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
2014 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
2015 # dhcp
2016 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
2017 self.profile['ip'] = self.ip_entry.get_text().strip()
2018 self.profile['netmask'] = self.netmask_entry.get_text().strip()
2019 self.profile['gateway'] = self.gw_entry.get_text().strip()
2020 self.profile['domain'] = self.domain_entry.get_text().strip()
2021 self.profile['dns1'] = self.dns1_entry.get_text().strip()
2022 self.profile['dns2'] = self.dns2_entry.get_text().strip()
2023 return self.profile
2024 return None
2026 # Remove profile dialog.
2028 #Parameters:
2030 # nothing
2032 #Returns:
2034 # nothing
2035 def destroy( self ):
2036 self.dialog.destroy()
2037 del self.dialog
2039 # Respond to expanding/hiding IP segment.
2041 #Parameters:
2043 # 'widget' -- gtk.Widget - The widget sending the event.
2045 # 'data' -- tuple - List of arbitrary arguments (not used)
2047 #Returns:
2049 # nothing
2050 def toggle_use_dhcp( self, widget, data = None ):
2051 expanded = self.dhcp_expander.get_expanded()
2052 if expanded:
2053 self.dhcp_expander.set_label( USE_IP_LABEL )
2054 else:
2055 self.dhcp_expander.set_label( USE_DHCP_LABEL )
2057 # Respond to expanding/hiding WPA segment.
2059 #Parameters:
2061 # 'widget' -- gtk.Widget - The widget sending the event.
2063 # 'data' -- tuple - List of arbitrary arguments (not used)
2065 #Returns:
2067 # nothing
2068 def toggle_use_wpa( self, widget, data = None ):
2069 expanded = self.wpa_expander.get_expanded()
2070 if expanded:
2071 self.wpa_expander.set_label( USE_WPA_LABEL )
2072 else:
2073 self.wpa_expander.set_label( NO_WPA_LABEL )
2075 # Return the index where item matches a cell in array.
2077 #Parameters:
2079 # 'item' -- string - Item to find in array
2081 # 'array' -- list - List in which to find match.
2083 #Returns:
2085 # integer - 0 (no match) or higher (index of match)
2086 def get_array_index( self, item, array ):
2087 try:
2088 return array.index( item.strip() )
2089 except:
2090 pass
2091 return 0
2093 # Return the value in array[ index ]
2095 #Parameters:
2097 # 'index' -- integer - The index to look up.
2099 # 'array' -- list - List in which to look up value.
2101 #Returns:
2103 # string -- empty string (no match) or looked up value
2104 def get_array_item( self, index, array ):
2105 try:
2106 return array[ index ]
2107 except:
2108 pass
2109 return ''
2112 # A simple class for putting up a "Please wait" dialog so the user
2113 # doesn't think we've forgotten about them. Implements the status interface.
2114 class StatusWindow:
2115 # Create a new StatusWindow.
2117 #Parameters:
2119 # 'parent' -- gtk.Object - Usually, the calling window.
2121 #Returns:
2123 # StatusWindow instance
2125 #NOTE:
2127 # Sample implementation of status interface. Status interface
2128 #requires .show(), .update_message(message), and .hide() methods.
2129 def __init__( self, parent ):
2130 global wifi_radar_icon
2131 self.parent = parent
2132 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2133 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2134 self.dialog.set_icon( icon )
2135 self.lbl = gtk.Label("Please wait...")
2136 self.bar = gtk.ProgressBar()
2137 self.dialog.vbox.pack_start(self.lbl)
2138 self.dialog.vbox.pack_start(self.bar)
2139 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2140 self.timer = None
2142 # Change the message displayed to the user.
2144 #Parameters:
2146 # 'message' -- string - The message to show to the user.
2148 #Returns:
2150 # nothing
2151 def update_message( self, message ):
2152 self.lbl.set_text(message)
2154 # Update the StatusWindow progress bar.
2156 #Parameters:
2158 # nothing
2160 #Returns:
2162 # True -- always return True
2163 def update_window( self ):
2164 self.bar.pulse()
2165 return True
2167 # Display and operate the StatusWindow.
2169 #Parameters:
2171 # nothing
2173 #Returns:
2175 # nothing
2176 def run( self ):
2177 pass
2179 # Show all the widgets of the StatusWindow.
2181 #Parameters:
2183 # nothing
2185 #Returns:
2187 # nothing
2188 def show( self ):
2189 self.dialog.show_all()
2190 self.timer = gobject.timeout_add(250, self.update_window)
2191 return False
2193 # Hide all the widgets of the StatusWindow.
2195 #Parameters:
2197 # nothing
2199 #Returns:
2201 # nothing
2202 def hide( self ):
2203 if self.timer:
2204 gobject.source_remove(self.timer)
2205 self.timer = None
2206 self.dialog.hide_all()
2207 return False
2209 # Remove the StatusWindow.
2211 #Parameters:
2213 # nothing
2215 #Returns:
2217 # nothing
2218 def destroy( self ):
2219 if self.timer:
2220 gobject.source_remove(self.timer)
2221 self.dialog.destroy()
2222 del self.dialog
2225 # Manage a GTK About Dialog
2226 class about_dialog(gtk.AboutDialog):
2227 # Subclass GTK AboutDialog
2229 #Parameters:
2231 # nothing
2233 #Returns:
2235 # nothing
2236 def __init__( self ):
2237 global wifi_radar_icon
2239 gtk.AboutDialog.__init__(self)
2240 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"])
2241 self.set_comments("WiFi connection manager")
2242 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2243 self.set_documenters(["Gary Case"])
2244 license = """
2245 This program is free software; you can redistribute it and/or modify
2246 it under the terms of the GNU General Public License as published by
2247 the Free Software Foundation; either version 2 of the License, or
2248 (at your option) any later version.
2250 This program is distributed in the hope that it will be useful,
2251 but WITHOUT ANY WARRANTY; without even the implied warranty of
2252 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2253 GNU General Public License for more details.
2255 You should have received a copy of the GNU General Public License
2256 along with this program; if not, write to the Free Software
2257 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2258 self.set_license(license)
2259 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2260 self.set_logo(logo)
2261 self.set_name("WiFi Radar")
2262 self.set_version(WIFI_RADAR_VERSION)
2263 self.set_website("http://wifi-radar.berlios.de")
2267 # Manage the configuration for the application, including reading and writing the config from/to a file.
2268 class ConfigFile(ConfigParser.SafeConfigParser):
2269 # Create a new ConfigFile.
2271 #Parameters:
2273 # 'filename' -- string - The configuration file's name.
2275 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2277 #Returns:
2279 # ConfigFile instance
2280 def __init__( self, filename, defaults, raw=False ):
2281 self.filename = filename
2282 self.raw = raw
2283 self.auto_profile_order = []
2284 ConfigParser.SafeConfigParser.__init__(self, defaults)
2286 # Set the contents of a section to values from a dictionary.
2288 #Parameters:
2290 # 'section_name' -- string - Configuration file section.
2292 # 'section_dict' -- dictionary - Values to add to section.
2294 #Returns:
2296 # nothing
2297 def set_section( self, section_name, section_dict ):
2298 try:
2299 self.add_section(section_name)
2300 except ConfigParser.DuplicateSectionError:
2301 pass
2302 for key in section_dict.keys():
2303 if type(section_dict[key]) == BooleanType:
2304 self.set_bool_opt(section_name + "." + key, section_dict[key])
2305 elif type(section_dict[key]) == IntType:
2306 self.set_int_opt(section_name + "." + key, section_dict[key])
2307 elif type(section_dict[key]) == FloatType:
2308 self.set_float_opt(section_name + "." + key, section_dict[key])
2309 else:
2310 self.set_opt(section_name + "." + key, section_dict[key])
2312 # Return the profile recorded in the specified section.
2314 #Parameters:
2316 # 'section_name' -- string - Configuration file section.
2318 #Returns:
2320 # dictionary or None - The specified profile or None if not found
2321 def get_profile( self, section_name ):
2322 if section_name in self.profiles():
2323 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' ]
2324 bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
2325 int_types = [ 'signal' ]
2326 profile = get_new_profile()
2327 for option in bool_types:
2328 option_tmp = self.get_opt_as_bool(section_name + "." + option)
2329 if option_tmp:
2330 profile[option] = option_tmp
2331 for option in int_types:
2332 option_tmp = self.get_opt_as_int(section_name + "." + option)
2333 if option_tmp:
2334 profile[option] = option_tmp
2335 for option in str_types:
2336 option_tmp = self.get_opt(section_name + "." + option)
2337 if option_tmp:
2338 profile[option] = option_tmp
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()