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