BUG FIX: also use sane default shellcmd env vars for connect post-script
[wifi-radar.git] / wifi-radar
blob7d7e10fedf61935aceb8075586d01ef38d6789a2
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.
26 import ConfigParser
27 import gtk
28 import os
29 import Queue
30 import re
31 import string
32 import sys
33 import threading
34 from signal import SIGTERM
35 from subprocess import call, Popen, PIPE
36 from time import sleep
37 from types import *
39 WIFI_RADAR_VERSION = "0.0.0"
41 if __debug__:
42 print '__debug__ is True'
43 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
44 # turn on debugging.
47 # Where the conf file should live could be different for your distro. Please change
48 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
50 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
52 os.environ['LC_MESSAGES'] = 'C'
55 #####################################
56 # Labels
57 USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
58 USE_IP_LABEL = "Manual network configuration"
59 WIFI_SET_LABEL = "WiFi Options"
60 CON_PP_LABEL = "Connection Commands"
61 DIS_PP_LABEL = "Disconnection Commands"
62 USE_WPA_LABEL = "Use WPA"
63 NO_WPA_LABEL = "No WPA"
64 ####################################################################################################
66 ####################################################################################################
67 ####################################################################################################
69 # Sets the interface to the specified network device
71 #Parameters:
73 # 'device' -- string - The network device to use
75 #Returns:
77 # nothing
78 def set_network_device( device ):
79 #print "set_network_device: ", device
80 if device != "auto_detect":
81 confFile.set_opt('DEFAULT.interface', device)
82 else: # auto detect network device
83 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
84 # If no devices are found, default to eth1.
85 # call iwconfig command and read output
86 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
87 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
88 if len(wireless_devices) > 0:
89 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
90 #else:
91 #print "No wifi-device found. Exiting."
92 #sys.exit()
94 # Return a blank profile
96 #Parameters:
98 # none
100 #Returns:
102 # dictionary -- An AP profile with defaults set.
103 def get_new_profile():
104 return { 'known': False,
105 'available': False,
106 'encrypted': False,
107 'essid': '',
108 'bssid': '',
109 'protocol': 'g',
110 'signal': 0,
111 'channel': 'auto',
112 'con_prescript': '',
113 'con_postscript': '',
114 'dis_prescript': '',
115 'dis_postscript': '',
116 'key': '',
117 'mode': '',
118 'security': '',
119 'use_wpa': False,
120 'wpa_driver': '',
121 'use_dhcp': True,
122 'ip': '',
123 'netmask': '',
124 'gateway': '',
125 'domain': '',
126 'dns1': '',
127 'dns2': ''
130 # Combine essid and bssid to make a config file section name
132 #Parameters:
134 # 'essid' -- string - AP ESSID
136 # 'bssid' -- string - AP BSSID
138 #Returns:
140 # string -- the bssid concatenated to a colon, concatenated to the essid
141 def make_section_name( essid, bssid ):
142 return essid + ':' + bssid
144 # Split a config file section name into an essid and a bssid
146 #Parameters:
148 # 'section' -- string - Config file section name
150 #Returns:
152 # list -- the essid and bssid
153 def split_section_name( section ):
154 parts = re.split(':', section)
155 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
157 # Run commands through the shell
159 #Parameters:
161 # 'command' -- tuple - The command and arguments to run.
163 # 'environment' -- dictionary - Environment variables (as keys) and their values.
165 #Returns:
167 # boolean -- True on success, otherwise, False
168 def shellcmd( command, environment = None ):
169 try:
170 env_tmp = os.environ
171 env_tmp.update(environment)
172 command = ' '.join(command)
173 return_code = call(command, shell=True, env=env_tmp)
174 if return_code >= 0:
175 return True
176 else:
177 print >>sys.stderr, "Child was terminated by signal", -return_code
178 except OSError, exception:
179 print >>sys.stderr, "Execution failed:", exception
180 return False
182 # Speak feedback message to user
184 #Parameters:
186 # 'words' -- string - Message to speak to user
188 #Returns:
190 # nothing
191 def say( words ):
192 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
193 words = words.replace( "\"", "\\\"" )
194 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
196 # Scan for a limited time and return AP names and bssid found.
197 # Access points we find will be put on the outgoing Queue, apQueue.
199 #Parameters:
201 # 'confFile' -- ConfigFile - Config file object
203 # 'apQueue' -- Queue - Queue on which to put AP profiles
205 # 'commandQueue' -- Queue - Queue from which to read commands
207 #Returns:
209 # nothing
210 def scanning_thread( confFile, apQueue, commandQueue ):
211 # Setup our essid pattern matcher
212 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
213 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abg]+)", re.I | re.M | re.S )
214 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
215 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
216 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
217 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
219 access_points = {}
220 command = "scan"
221 while True:
222 try:
223 command = commandQueue.get_nowait()
224 if __debug__: print "scanning_thread received command:", command
225 command_read = True
226 except Queue.Empty:
227 command_read = False
228 if command == "scan":
229 #if __debug__: print "Beginning scan pass"
230 # Some cards need to have the interface up to scan
231 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
232 # call ifconfig command and wait for return
233 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
234 # update the signal strengths
235 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
236 #if __debug__:
237 #print "Current IP ", get_current_ip()
238 #print "Current ESSID ", get_current_essid()
239 #print "Current BSSID ", get_current_bssid()
240 # zero out the signal levels for all access points
241 for bssid in access_points:
242 access_points[bssid]['signal'] = 0
243 # split the scan data based on the address line
244 hits = scandata.split(' - ')
245 for hit in hits:
246 # set the defaults for profile template
247 profile = get_new_profile()
248 m = essid_pattern.search( hit )
249 if m:
250 # we found an essid
251 profile['essid'] = m.groups()[1]
252 m = bssid_pattern.search( hit ) # get BSSID from scan
253 if m: profile['bssid'] = m.groups()[1]
254 m = protocol_pattern.search( hit ) # get protocol from scan
255 if m: profile['protocol'] = m.groups()[1]
256 m = mode_pattern.search( hit ) # get mode from scan
257 if m: profile['mode'] = m.groups()[1]
258 m = channel_pattern.search( hit ) # get channel from scan
259 if m: profile['channel'] = m.groups()[1]
260 m = enckey_pattern.search( hit ) # get encryption key from scan
261 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
262 m = signal_pattern.search( hit ) # get signal strength from scan
263 if m: profile['signal'] = m.groups()[1]
264 access_points[ profile['bssid'] ] = profile
265 for bssid in access_points:
266 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
267 # Put all, now or previously, sensed access_points into apQueue
268 try:
269 apQueue.put_nowait( access_points[bssid] )
270 except Queue.Full:
271 pass
272 elif command == "exit":
273 if __debug__: print "Exiting scanning_thread"
274 return
275 if command_read: commandQueue.task_done()
276 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
277 sleep( 3 )
278 else:
279 sleep( 1 )
282 # Manage a connection; including reporting connection state,
283 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
284 class ConnectionManager():
285 # Create a new connection manager which can read a config file and send to scanning thread
286 # command Queue. A new manager checks for a pre-existing connection and takes
287 # its AP profile from the ESSID and BSSID to which it is currently attached.
289 #Parameters:
291 # 'confFile' -- ConfigFile - Config file object
293 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
295 #Returns:
297 # ConnectionManager instance
298 def __init__( self, confFile, commandQueue ):
299 self.confFile = confFile
300 self.commQueue = commandQueue
301 # is connection running?
302 self.state = False
303 if self.get_current_ip():
304 self.state = True
305 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
307 # Change the interface state: up or down.
309 #Parameters:
311 # 'state' -- string - The state to which to change the interface.
313 #Returns:
315 # nothing
316 def if_change( self, state ):
317 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
318 if __debug__: print "if_change: changing interface state to %s" % state
319 # call ifconfig command and wait for return
320 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
322 # Connect to the specified AP.
324 #Parameters:
326 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
328 # 'status' -- status implementer - Object which implements status interface.
330 #Returns:
332 # nothing
333 def connect_to_network( self, profile, status ):
334 self.profile = profile
335 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
336 say( msg )
337 if __debug__: print " %s" % msg
338 # ready to dance
339 # Let's run the connection prescript
340 if self.profile['con_prescript'].strip() != '':
341 # got something to execute
342 # run connection prescript through shell and wait for return
343 if __debug__: print "executing connection prescript:", self.profile['con_prescript']
344 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
345 status.show()
346 # Some cards need to have the interface up
347 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
348 self.if_change('up')
349 # Start building iwconfig command line, command
350 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
351 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
352 # Setting essid
353 iwconfig_command.append( 'essid' )
354 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
355 # Setting nick
356 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
357 # Setting key
358 iwconfig_command.append( 'key' )
359 if self.profile['key'] == '':
360 iwconfig_command.append( 'off' )
361 else:
362 iwconfig_command.append( "'" + self.profile['key'] + "'" )
363 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
364 # Setting mode to Master, this will cause association with the AP to fail
365 #iwconfig_command.append( 'mode' )
366 #iwconfig_command.append( 'Master' )
367 # Setting channel
368 if self.profile['channel'] != '':
369 iwconfig_command.append( 'channel' )
370 iwconfig_command.append( self.profile['channel'] )
371 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
372 iwconfig_command.append( 'ap' )
373 iwconfig_command.append( self.profile['bssid'] )
374 # Some cards require a commit
375 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
376 if __debug__: print 'iwconfig_args %s ' % ( iwconfig_args, )
377 iwconfig_command.append( 'commit' )
378 # call iwconfig command and wait for return
379 if not shellcmd(iwconfig_command): return
380 # Now normal network stuff
381 # Kill off any existing DHCP clients running
382 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
383 if __debug__: print "Killing existing DHCP..."
384 try:
385 if self.confFile.get_opt('DHCP.kill_args') != '':
386 # call DHCP client kill command and wait for return
387 if __debug__: print self.confFile.get_opt('DHCP.command') + " " + self.confFile.get_opt('DHCP.kill_args')
388 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
389 else:
390 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
391 except OSError:
392 print "failed to kill DHCP client"
393 sys.exit()
394 finally:
395 print "Stale pid file. Removing..."
396 os.remove(self.confFile.get_opt('DHCP.pidfile'))
397 # Kill off any existing WPA supplicants running
398 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
399 if __debug__: print "Killing existing WPA supplicant..."
400 try:
401 if not self.confFile.get_opt('WPA.kill_command') != '':
402 # call WPA supplicant kill command and wait for return
403 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
404 else:
405 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
406 except OSError:
407 print "failed to kill WPA supplicant"
408 sys.exit()
409 finally:
410 print "Stale pid file. Removing..."
411 os.remove(self.confFile.get_opt('WPA.pidfile'))
412 # Begin WPA supplicant
413 if self.profile['use_wpa'] :
414 if __debug__: print "WPA args: %s" % ( wpa_options, )
415 status.update_message("WPA supplicant starting")
416 if sys.modules.has_key("gtk"):
417 while gtk.events_pending():
418 gtk.main_iteration(False)
419 # call WPA supplicant command and do not wait for return
420 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
421 if self.profile['use_dhcp'] :
422 if __debug__: print "Disable iwlist while dhcp in progress..."
423 try:
424 self.commQueue.put("pause")
425 except Queue.Full:
426 pass
427 status.update_message("Acquiring IP Address (DHCP)")
428 if sys.modules.has_key("gtk"):
429 while gtk.events_pending():
430 gtk.main_iteration(False)
431 # call DHCP client command and do not wait for return
432 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
433 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
434 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
435 dhcp_proc = Popen(dhcp_command, stdout=None)
436 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
437 tick = 0.25
438 waiting = dhcp_proc.poll()
439 while waiting == None:
440 waiting = dhcp_proc.poll()
441 if timer < 0:
442 os.kill(dhcp_proc.pid, SIGTERM)
443 break
444 if sys.modules.has_key("gtk"):
445 while gtk.events_pending():
446 gtk.main_iteration(False)
447 timer -= tick
448 sleep(tick)
449 if not self.get_current_ip():
450 status.update_message("Could not get IP address!")
451 if sys.modules.has_key("gtk"):
452 while gtk.events_pending():
453 gtk.main_iteration(False)
454 sleep(1)
455 if self.state:
456 self.disconnect_interface()
457 else:
458 status.update_message("Got IP address. Done.")
459 self.state = True
460 if sys.modules.has_key("gtk"):
461 while gtk.events_pending():
462 gtk.main_iteration(False)
463 sleep(2)
464 # Re-enable iwlist
465 try:
466 self.commQueue.put("scan")
467 except Queue.Full:
468 pass
469 else:
470 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'] )
471 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
472 resolv_contents = ''
473 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
474 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
475 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
476 if ( resolv_contents != '' ):
477 resolv_file=open('/etc/resolv.conf', 'w')
478 resolv_file.write(s)
479 resolv_file.close
480 if not shellcmd([ifconfig_command]): return
481 if not shellcmd([route_command]): return
482 self.state = True
483 # Let's run the connection postscript
484 con_postscript = self.profile['con_postscript']
485 if self.profile['con_postscript'].strip() != '':
486 if __debug__: print "executing connection postscript:", self.profile['con_postscript']
487 shellcmd([self.profile['con_postscript']],
488 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
489 "WIFIRADAR_ESSID": self.get_current_essid() or '',
490 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
491 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
494 status.hide()
496 # Disconnect from the AP with which a connection has been established/attempted.
498 #Parameters:
500 # nothing
502 #Returns:
504 # nothing
505 def disconnect_interface( self ):
506 msg = "Disconnecting"
507 say( msg )
508 if __debug__: print msg
509 # Pause scanning while manipulating card
510 try:
511 self.commQueue.put("pause")
512 except Queue.Full:
513 pass
514 if self.state:
515 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
516 # Let's run the disconnection prescript
517 if self.profile['dis_prescript'].strip() != '':
518 if __debug__: print "executing disconnection prescript:", self.profile['dis_prescript']
519 shellcmd([self.profile['dis_prescript']],
520 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
521 "WIFIRADAR_ESSID": self.get_current_essid() or '',
522 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
523 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
526 if __debug__: print "Kill off any existing DHCP clients running..."
527 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
528 if __debug__: print "Killing existing DHCP..."
529 try:
530 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
531 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
532 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
533 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
534 if __debug__: print "DHCP command", dhcp_command
535 # call DHCP client command and wait for return
536 if not shellcmd(dhcp_command): return
537 else:
538 if __debug__: print "killing DHCP manually."
539 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
540 except OSError:
541 print "failed to kill DHCP client"
542 if __debug__: print "Kill off any existing WPA supplicants running..."
543 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
544 if __debug__: print "Killing existing WPA supplicant..."
545 try:
546 if not self.confFile.get_opt('WPA.kill_command') != '':
547 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
548 if not shellcmd(wpa_command): return
549 else:
550 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
551 except OSError:
552 print "failed to kill WPA supplicant"
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 if __debug__: print "Let's clear out the wireless stuff"
555 if __debug__: print 'Now take the interface down'
556 # taking down the interface too quickly can crash my system, so pause a moment
557 sleep(1)
558 self.if_change('down')
559 if __debug__: print 'Since it may be brought back up by the next scan, lets unset its IP'
560 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
561 # Let's run the disconnection postscript
562 if self.profile['dis_postscript'].strip() != '':
563 if __debug__: print "executing disconnection postscript:", self.profile['dis_postscript']
564 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
565 self.state = False
566 if __debug__: print 'Disconnect complete.'
567 # Begin scanning again
568 try:
569 self.commQueue.put("scan")
570 except Queue.Full:
571 pass
573 # Returns the current IP, if any, by calling ifconfig.
575 #Parameters:
577 # nothing
579 #Returns:
581 # string or None -- the IP address or None (if no there is no current connection)
582 def get_current_ip( self ):
583 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
584 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
585 # Be careful to the language (inet adr: in French for example)
587 # Hi Brian
589 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
590 # There the string in ifconfig is inet Adresse for the IP which isn't
591 # found by the current get_current_ip function in wifi-radar. I changed
592 # the according line (#289; gentoo, v1.9.6-r1) to
593 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
594 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
596 # I'd be happy if you could incorporate this small change because as now
597 # I've got to change the file every time it is updated.
599 # Best wishes
601 # Simon
602 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
603 line = ifconfig_info.read()
604 if ip_re.search( line ):
605 return ip_re.search( line ).group(1)
606 return None
608 # Returns the current ESSID, if any, by calling iwconfig.
610 #Parameters:
612 # nothing
614 #Returns:
616 # string or None -- the ESSID or None (if no there is no current association)
617 def get_current_essid( self ):
618 """Returns the current ESSID if any by calling iwconfig"""
619 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
620 # Be careful to the language (inet adr: in French for example)
621 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
622 line = iwconfig_info.read()
623 if essid_re.search( line ):
624 return essid_re.search( line ).group(2)
625 return None
627 # Returns the current BSSID, if any, by calling iwconfig.
629 #Parameters:
631 # nothing
633 #Returns:
635 # string or None -- the BSSID or None (if no there is no current association)
636 def get_current_bssid( self ):
637 """Returns the current BSSID if any by calling iwconfig"""
638 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
639 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
640 line = iwconfig_info.read()
641 if bssid_re.search( line ):
642 return bssid_re.search( line ).group(2)
643 return None
647 # The main user interface window for WiFi Radar. This class also is the control
648 # center for most of the rest of the operations.
649 class radar_window:
650 # Create a new radar_window.
652 #Parameters:
654 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
656 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
658 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
660 #Returns:
662 # radar_window instance
663 def __init__( self, confFile, apQueue, commQueue ):
664 global signal_xpm_none
665 global signal_xpm_low
666 global signal_xpm_barely
667 global signal_xpm_ok
668 global signal_xpm_best
669 global known_profile_icon
670 global unknown_profile_icon
671 global wifi_radar_icon
673 self.confFile = confFile
674 self.apQueue = apQueue
675 self.commandQueue = commQueue
676 self.access_points = {}
677 self.connection = None
679 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
680 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
681 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
682 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
683 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
684 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
685 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
686 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
687 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
688 self.window.set_icon( icon )
689 self.window.set_border_width( 10 )
690 self.window.set_size_request( 550, 300 )
691 self.window.set_title( "WiFi Radar" )
692 self.window.connect( 'delete_event', self.delete_event )
693 self.window.connect( 'destroy', self.destroy )
694 # let's create all our widgets
695 self.current_network = gtk.Label()
696 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
697 self.current_network.show()
698 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
699 self.close_button.show()
700 self.close_button.connect( 'clicked', self.delete_event, None )
701 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
702 self.about_button.show()
703 self.about_button.connect( 'clicked', self.show_about_info, None )
704 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
705 self.preferences_button.show()
706 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
707 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
708 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
709 self.plist = gtk.TreeView( self.pstore )
710 # The icons column, known and encryption
711 self.pix_cell = gtk.CellRendererPixbuf()
712 self.wep_cell = gtk.CellRendererPixbuf()
713 self.icons_cell = gtk.CellRendererText()
714 self.icons_col = gtk.TreeViewColumn()
715 self.icons_col.pack_start( self.pix_cell, False )
716 self.icons_col.pack_start( self.wep_cell, False )
717 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
718 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
719 self.plist.append_column( self.icons_col )
720 # The AP column
721 self.ap_cell = gtk.CellRendererText()
722 self.ap_col = gtk.TreeViewColumn( "Access Point" )
723 self.ap_col.pack_start( self.ap_cell, True )
724 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
725 self.plist.append_column( self.ap_col )
726 # The signal column
727 self.sig_cell = gtk.CellRendererPixbuf()
728 self.signal_col = gtk.TreeViewColumn( "Signal" )
729 self.signal_col.pack_start( self.sig_cell, True )
730 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
731 self.plist.append_column( self.signal_col )
732 # The mode column
733 self.mode_cell = gtk.CellRendererText()
734 self.mode_col = gtk.TreeViewColumn( "Mode" )
735 self.mode_col.pack_start( self.mode_cell, True )
736 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
737 self.plist.append_column( self.mode_col )
738 # The protocol column
739 self.prot_cell = gtk.CellRendererText()
740 self.protocol_col = gtk.TreeViewColumn( "802.11" )
741 self.protocol_col.pack_start( self.prot_cell, True )
742 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
743 self.plist.append_column( self.protocol_col )
744 # The channel column
745 self.channel_cell = gtk.CellRendererText()
746 self.channel_col = gtk.TreeViewColumn( "Channel" )
747 self.channel_col.pack_start( self.channel_cell, True )
748 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
749 self.plist.append_column( self.channel_col )
750 # DnD Ordering
751 self.plist.set_reorderable( True )
752 self.pstore.connect( 'row-changed', self.update_auto_profile_order )
753 # enable/disable buttons based on the selected network
754 self.selected_network = self.plist.get_selection()
755 self.selected_network.connect( 'changed', self.on_network_selection, None )
756 # the list scroll bar
757 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
758 sb.show()
759 self.plist.show()
760 # Add New button
761 self.new_button = gtk.Button( "_New" )
762 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
763 self.new_button.show()
764 # Add Configure button
765 self.edit_button = gtk.Button( "C_onfigure" )
766 self.edit_button.connect( 'clicked', self.edit_profile, None )
767 self.edit_button.show()
768 self.edit_button.set_sensitive(False)
769 # Add Delete button
770 self.delete_button = gtk.Button( "_Delete" )
771 self.delete_button.connect( 'clicked', self.delete_profile, None )
772 self.delete_button.show()
773 self.delete_button.set_sensitive(False)
774 # Add Connect button
775 self.connect_button = gtk.Button( "Co_nnect" )
776 self.connect_button.connect( 'clicked', self.connect_profile, None )
777 # Add Disconnect button
778 self.disconnect_button = gtk.Button( "D_isconnect" )
779 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
780 # lets add our widgets
781 rows = gtk.VBox( False, 3 )
782 net_list = gtk.HBox( False, 0 )
783 listcols = gtk.HBox( False, 0 )
784 prows = gtk.VBox( False, 0 )
785 # lets start packing
786 # the network list
787 net_list.pack_start( self.plist, True, True, 0 )
788 net_list.pack_start( sb, False, False, 0 )
789 # the rows level
790 rows.pack_start( net_list , True, True, 0 )
791 rows.pack_start( self.current_network, False, True, 0 )
792 # the list columns
793 listcols.pack_start( rows, True, True, 0 )
794 listcols.pack_start( prows, False, False, 5 )
795 # the list buttons
796 prows.pack_start( self.new_button, False, False, 2 )
797 prows.pack_start( self.edit_button, False, False, 2 )
798 prows.pack_start( self.delete_button, False, False, 2 )
799 prows.pack_end( self.connect_button, False, False, 2 )
800 prows.pack_end( self.disconnect_button, False, False, 2 )
802 self.window.action_area.pack_start( self.about_button )
803 self.window.action_area.pack_start( self.preferences_button )
804 self.window.action_area.pack_start( self.close_button )
806 rows.show()
807 prows.show()
808 listcols.show()
809 self.window.vbox.add( listcols )
810 self.window.vbox.set_spacing( 3 )
811 self.window.show_all()
813 # Now, immediately hide these two. The proper one will be
814 # displayed later, based on interface state. -BEF-
815 self.disconnect_button.hide()
816 self.connect_button.hide()
817 self.connect_button.set_sensitive(False)
819 # set up connection manager for later use
820 self.connection = ConnectionManager( self.confFile, self.commandQueue )
821 # set up status window for later use
822 self.status_window = StatusWindow( self )
823 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
825 # Add our known profiles in order
826 for ap in self.confFile.auto_profile_order:
827 ap = ap.strip()
828 self.access_points[ ap ] = self.confFile.get_profile( ap )
829 wep = None
830 if self.access_points[ ap ]['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
831 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'] ] )
832 # This is the first run (or, at least, no config file was present), so pop up the preferences window
833 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
834 self.confFile.remove_option('DEFAULT', 'new_file')
835 self.edit_preferences(self.preferences_button)
837 # Begin running radar_window in Gtk event loop.
839 #Parameters:
841 # nothing
843 #Returns:
845 # nothing
846 def main( self ):
847 gtk.main()
849 # Write the config file to disk and quit application.
851 #Parameters:
853 # 'widget' -- gtk.Widget - The widget sending the event.
855 #Returns:
857 # nothing
858 def destroy( self, widget = None):
859 if self.status_window:
860 self.status_window.destroy()
861 gtk.main_quit()
863 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
865 #Parameters:
867 # 'widget' -- gtk.Widget - The widget sending the event.
869 # 'data' -- tuple - list of arbitrary arguments (not used)
871 #Returns:
873 # boolean -- always return False (i.e. do not propigate the signal which called)
874 def delete_event( self, widget, data = None ):
875 # Put "exit" on the command Queue and wait indefinitely for it to be accepted
876 try:
877 self.commandQueue.put("exit", True)
878 except Queue.Full:
879 pass
880 # Save the preferred networks order
881 self.update_auto_profile_order()
882 self.destroy()
883 return False
885 # Updates the on-screen profiles list.
887 #Parameters:
889 # nothing
891 #Returns:
893 # boolean -- always return True
894 def update_plist_items( self ):
895 # Indicate to PyGtk that only one Gtk thread should run here
896 gtk.gdk.threads_enter()
897 # update the current ip and essid
898 # set the state of connect/disconnect buttons based on whether we have an IP address
899 if self.connection:
900 if self.connection.state:
901 self.current_network.set_text( "Connected to %s\nIP Address %s" % ( make_section_name( self.connection.get_current_essid(), self.connection.get_current_bssid() ), self.connection.get_current_ip() ) )
902 self.connect_button.hide()
903 self.disconnect_button.show()
904 else:
905 self.current_network.set_text( "Not Connected." )
906 self.disconnect_button.hide()
907 self.connect_button.show()
909 while True:
910 # Get profiles scanned by iwlist
911 try:
912 profile = self.apQueue.get_nowait()
913 except Queue.Empty:
914 break
915 else:
916 prow_iter = self.get_row_by_ap( profile['essid'], profile['bssid'] )
917 wep = None
918 if prow_iter != None:
919 # the AP is in the list of APs on the screen
920 apname = make_section_name(profile['essid'], profile['bssid'])
921 if self.access_points.has_key(apname):
922 # This AP has been configured and is/should be stored in the config file
923 profile['known'] = self.access_points[apname]['known']
924 self.access_points[apname]['available'] = profile['available']
925 self.access_points[apname]['encrypted'] = profile['encrypted']
926 self.access_points[apname]['signal'] = profile['signal']
927 self.access_points[apname]['mode'] = profile['mode']
928 self.access_points[apname]['protocol'] = profile['protocol']
929 self.access_points[apname]['channel'] = profile['channel']
930 # Set the 'known' values; False is default, overridden to True by self.access_points
931 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known( profile[ 'known' ] ))
932 self.pstore.set_value(prow_iter, 2, profile[ 'known' ])
933 self.pstore.set_value(prow_iter, 3, profile['available'])
934 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
935 self.pstore.set_value(prow_iter, 4, wep)
936 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal( profile['signal'] ))
937 self.pstore.set_value(prow_iter, 6, profile['mode'])
938 self.pstore.set_value(prow_iter, 7, profile['protocol'])
939 self.pstore.set_value(prow_iter, 8, profile['channel'])
940 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
941 #for val in self.pstore[prow_iter]:
942 #print val,
943 else:
944 # the AP is not in the list of APs on the screen
945 self.pstore.append( [ profile[ 'essid' ] + "\n" + profile['bssid'], self.pixbuf_from_known( profile['known'] ), profile['known'], profile['available'], wep, self.pixbuf_from_signal( profile['signal'] ), profile['mode'], profile['protocol'], profile['channel'] ] )
946 #print "update_plist_items: new profile", profile[ 'essid' ], profile['bssid']
947 # Allow other Gtk threads to run
948 gtk.gdk.threads_leave()
949 #print "update_plist_items: Empty apQueue"
950 return True
952 # Return the proper icon for a value of known.
954 #Parameters:
956 # 'known' -- boolean - Whether the AP is known (i.e. configured)
958 #Returns:
960 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
961 def pixbuf_from_known( self, known ):
962 """ return the proper icon for value of known """
963 if known:
964 return self.known_profile_icon
965 else:
966 return self.unknown_profile_icon
968 # Return an icon indicating the signal level.
970 #Parameters:
972 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
974 #Returns:
976 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
977 def pixbuf_from_signal( self, signal ):
978 signal = int( signal )
979 #print "signal level:", signal
980 if signal < 3:
981 return self.signal_none_pb
982 elif signal < 12:
983 return self.signal_low_pb
984 elif signal < 20:
985 return self.signal_barely_pb
986 elif signal < 35:
987 return self.signal_ok_pb
988 elif signal >= 35:
989 return self.signal_best_pb
990 else:
991 return None
993 # Return row which holds specified ESSID and BSSID.
995 #Parameters:
997 # 'essid' -- string - ESSID to match
999 # 'bssid' -- string - BSSID to match
1001 #Returns:
1003 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1004 def get_row_by_ap( self, essid, bssid ):
1005 for row in self.pstore:
1006 if ( row[0] == essid + "\n" + bssid ):
1007 #print "matched:", row.iter, essid, bssid
1008 return row.iter
1009 return None
1011 # Enable/disable buttons based on the selected network.
1013 #Parameters:
1015 # 'widget' -- gtk.Widget - The widget sending the event.
1017 # 'data' -- tuple - list of arbitrary arguments (not used)
1019 #Returns:
1021 # nothing
1022 def on_network_selection( self, widget, data = None ):
1023 ( store, selected_iter ) = self.selected_network.get_selected()
1024 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1025 # if no networks are selected, disable all buttons except New
1026 # (this occurs after a drag-and-drop)
1027 if selected_iter == None:
1028 self.edit_button.set_sensitive(False)
1029 self.delete_button.set_sensitive(False)
1030 self.connect_button.set_sensitive(False)
1031 return
1032 # enable/disable buttons
1033 self.connect_button.set_sensitive(True)
1034 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1035 self.edit_button.set_sensitive(True)
1036 self.delete_button.set_sensitive(True)
1037 else:
1038 self.edit_button.set_sensitive(True)
1039 self.delete_button.set_sensitive(False)
1041 # Init and run the about dialog
1043 #Parameters:
1045 # 'widget' -- gtk.Widget - The widget sending the event.
1047 # 'data' -- tuple - list of arbitrary arguments (not used)
1049 #Returns:
1051 # nothing
1052 def show_about_info( self, widget, data=None ):
1053 about = about_dialog()
1054 about.run()
1055 about.destroy()
1057 # Init and run the preferences 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 edit_preferences( self, widget, data=None ):
1069 # get raw strings from config file
1070 self.confFile.raw = True
1071 prefs = preferences_dialog( self, self.confFile )
1072 response = prefs.run()
1073 prefs.destroy()
1074 if response == int(gtk.RESPONSE_ACCEPT):
1075 prefs.save()
1076 # get cooked strings from config file
1077 self.confFile.raw = False
1079 # Respond to a request to create a new AP profile
1081 #Parameters:
1083 # 'widget' -- gtk.Widget - The widget sending the event.
1085 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1087 # 'data' -- tuple - list of arbitrary arguments (not used)
1089 #Returns:
1091 # boolean -- True if a profile was created and False if profile creation was canceled.
1092 def create_new_profile( self, widget, profile, data=None ):
1093 profile_editor = profile_dialog( self, profile )
1094 profile = profile_editor.run()
1095 profile_editor.destroy()
1096 if profile:
1097 apname = make_section_name( profile['essid'], profile['bssid'] )
1098 # Check that the ap does not exist already
1099 if apname in self.confFile.profiles():
1100 dlg = gtk.MessageDialog( self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "A profile for %s already exists" % (apname) )
1101 dlg.run()
1102 dlg.destroy()
1103 del dlg
1104 # try again
1105 self.access_points[ apname ] = profile
1106 self.confFile.set_section( apname, profile )
1107 # if it is not in the auto_profile_order add it
1108 if apname not in self.confFile.auto_profile_order:
1109 self.confFile.auto_profile_order.append(apname)
1110 # add to the store
1111 wep = None
1112 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1113 self.confFile.write()
1114 return True
1115 else:
1116 # Did not create new profile
1117 return False
1119 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1120 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1122 #Parameters:
1124 # 'widget' -- gtk.Widget - The widget sending the event.
1126 # 'data' -- tuple - list of arbitrary arguments (not used)
1128 #Returns:
1130 # nothing
1131 def edit_profile( self, widget, data=None ):
1132 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1133 if not selected_iter: return
1134 row_start = str(self.pstore.get_value( selected_iter, 0 )).split("\n")
1135 apname = make_section_name( row_start[0], row_start[1] )
1136 profile = self.confFile.get_profile( apname )
1137 if profile:
1138 profile_editor = profile_dialog( self, profile )
1139 profile = profile_editor.run()
1140 profile_editor.destroy()
1141 if profile:
1142 if __debug__:
1143 print "Got edited profile ", profile
1144 apname = make_section_name( profile['essid'], profile['bssid'] )
1145 self.access_points[ apname ] = profile
1146 self.confFile.set_section( apname, profile )
1147 self.confFile.write()
1148 else:
1149 profile = get_new_profile()
1150 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1151 self.create_new_profile( widget, profile, data )
1153 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1155 #Parameters:
1157 # 'widget' -- gtk.Widget - The widget sending the event.
1159 # 'data' -- tuple - list of arbitrary arguments (not used)
1161 #Returns:
1163 # nothing
1164 def delete_profile( self, widget, data=None ):
1165 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1166 if not selected_iter: return
1167 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1168 known = store.get_value( selected_iter, 1 )
1169 if not known: return
1170 dlg = gtk.MessageDialog(
1171 self.window,
1172 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1173 gtk.MESSAGE_QUESTION,
1174 gtk.BUTTONS_YES_NO,
1175 "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid) )
1176 res = dlg.run()
1177 dlg.destroy()
1178 del dlg
1179 if res == gtk.RESPONSE_NO: return
1180 # Remove it
1181 apname = make_section_name( essid, bssid )
1182 del self.access_points[ apname ]
1183 self.confFile.remove_section( apname )
1184 if __debug__: print "delete_profile: ", apname, ":", self.confFile.auto_profile_order
1185 if apname in self.confFile.auto_profile_order: self.confFile.auto_profile_order.remove(apname)
1186 self.pstore.remove( selected_iter )
1187 # Let's save our current state
1188 self.update_auto_profile_order()
1189 self.confFile.write()
1191 # Respond to a request to connect to an AP.
1193 #Parameters:
1195 # 'widget' -- gtk.Widget - The widget sending the event.
1197 # 'profile' -- dictionary - The AP profile to which to connect.
1199 # 'data' -- tuple - list of arbitrary arguments (not used)
1201 #Returns:
1203 # nothing
1204 def connect_profile( self, widget, profile, data=None ):
1205 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1206 if not selected_iter: return
1207 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1208 known = store.get_value( selected_iter, 2 )
1209 if not known:
1210 if data != 'noconnect':
1211 dlg = gtk.MessageDialog(
1212 self.window,
1213 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
1214 gtk.MESSAGE_QUESTION,
1215 gtk.BUTTONS_YES_NO,
1216 "This network does not have a profile configured.\n\nWould you like to create one now?" )
1217 res = dlg.run()
1218 dlg.destroy()
1219 del dlg
1220 if res == gtk.RESPONSE_NO: return
1221 profile = get_new_profile()
1222 profile['essid'] = essid
1223 profile['bssid'] = bssid
1224 if not self.create_new_profile( widget, profile, data ):
1225 return
1226 apname = make_section_name( essid, bssid )
1227 self.connection.connect_to_network(self.access_points[apname], self.status_window)
1229 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1231 #Parameters:
1233 # 'widget' -- gtk.Widget - The widget sending the event.
1235 # 'data' -- tuple - list of arbitrary arguments (not used)
1237 #Returns:
1239 # nothing
1240 def disconnect_profile( self, widget, data=None ):
1241 if data == "cancel":
1242 self.status_window.update_message("Canceling connection...")
1243 if sys.modules.has_key("gtk"):
1244 while gtk.events_pending():
1245 gtk.main_iteration(False)
1246 sleep(1)
1247 self.connection.disconnect_interface()
1249 # Update the config file auto profile order from the on-screen order
1251 #Parameters:
1253 # 'widget' -- gtk.Widget - The widget sending the event.
1255 # 'data' -- tuple - list of arbitrary arguments (not used)
1257 # 'data2' -- tuple - list of arbitrary arguments (not used)
1259 #Returns:
1261 # nothing
1262 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1263 # recreate the auto_profile_order
1264 auto_profile_order = []
1265 piter = self.pstore.get_iter_first()
1266 while piter:
1267 # only if it's known
1268 if self.pstore.get_value( piter, 2 ) == True:
1269 row_start = str(self.pstore.get_value( piter, 0 )).split("\n")
1270 auto_profile_order.append( make_section_name( row_start[0], row_start[1] ) )
1271 piter = self.pstore.iter_next( piter )
1272 self.confFile.auto_profile_order = auto_profile_order
1275 # Button to allow user to choose a file and put value into specified gtk.Entry
1276 class file_browse_button(gtk.Button):
1277 # Create a button to simulate a File/Open
1279 #Parameters:
1281 # 'parent' -- gtk.Object -- Usually, the calling window.
1283 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1285 #Returns:
1287 # file_browse_button instance
1288 def __init__( self, parent, entry ):
1289 self.parent_window = parent
1290 self.entry = entry
1291 gtk.Button.__init__(self, "Browse", None)
1292 #self.
1293 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)
1294 self.connect("clicked", self.browse_files)
1296 # Show filechooser dialog and get user selection
1298 #Parameters:
1300 # 'widget' -- gtk.Widget -- The widget sending the event.
1302 #Returns:
1304 # nothing
1306 #NOTES:
1308 # updates entry value
1310 def browse_files( self, widget ):
1311 self.browser_dialog.set_filename(self.entry.get_text())
1312 self.browser_dialog.run()
1313 self.entry.set_text(self.browser_dialog.get_filename())
1314 self.browser_dialog.destroy()
1317 # The preferences dialog. Edits non-profile sections of the config file.
1318 class preferences_dialog:
1319 # Create a new preferences_dialog.
1321 #Parameters:
1323 # 'parent' -- gtk.Object - Usually, the calling window.
1325 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1327 #Returns:
1329 # preferences_dialog instance
1330 def __init__( self, parent, confFile ):
1331 global wifi_radar_icon
1332 self.parent = parent
1333 self.confFile = confFile
1334 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1335 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1336 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1337 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1338 self.dialog.set_icon( icon )
1339 self.dialog.set_resizable( True )
1340 self.dialog.set_transient_for( self.parent.window )
1341 self.tooltips = gtk.Tooltips()
1343 # set up preferences widgets
1345 # build everything in a tabbed notebook
1346 self.prefs_notebook = gtk.Notebook()
1348 ### General tab
1349 self.general_page = gtk.VBox()
1350 # auto detect wireless device
1351 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1353 print "prefs: ", self.confFile.get_opt('DEFAULT.interface')
1355 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1356 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1357 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1358 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1360 # network interface selecter
1361 self.w_interface = gtk.combo_box_entry_new_text()
1362 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1363 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1364 for device in wireless_devices:
1365 if device != self.confFile.get_opt('DEFAULT.interface'):
1366 self.w_interface.append_text(device)
1367 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1368 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1369 self.w_interface.set_active(0)
1370 self.w_interface_label = gtk.Label("Wireless device")
1371 self.w_hbox1 = gtk.HBox(False, 0)
1372 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1373 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1374 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1375 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1377 # scan timeout (spin button of integers from 1 to 100)
1378 self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,1 )
1379 self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1380 self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1381 self.w_scan_timeout.set_numeric(True)
1382 self.w_scan_timeout.set_snap_to_ticks(True)
1383 self.w_scan_timeout.set_wrap(False)
1384 self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1385 self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1386 self.w_hbox2 = gtk.HBox(False, 0)
1387 self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1388 self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1389 self.general_page.pack_start(self.w_hbox2, False, False, 5)
1391 # speak up
1392 self.w_speak_up = gtk.CheckButton("Use speak-up")
1393 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1394 self.w_speak_up.connect("toggled", self.toggle_speak)
1395 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1396 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1398 # speak up command
1399 self.w_speak_cmd = gtk.Entry()
1400 self.w_speak_cmd.set_width_chars(16)
1401 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1402 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1403 self.w_speak_cmd_label = gtk.Label("Speak Command")
1404 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1405 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1406 self.w_hbox3 = gtk.HBox(False, 0)
1407 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1408 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1409 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1410 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1411 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1413 # commit required
1414 self.w_commit_required = gtk.CheckButton("Commit required")
1415 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1416 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1417 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1419 # ifup required
1420 self.w_ifup_required = gtk.CheckButton("Ifup required")
1421 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1422 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1423 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1425 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1426 ### End of General tab
1428 ### Advanced tab
1429 # table to use for layout of following command configurations
1430 self.cmds_table = gtk.Table()
1432 # ifconfig command
1433 self.ifconfig_cmd = gtk.Entry()
1434 self.ifconfig_cmd.set_width_chars(32)
1435 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1436 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1437 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1438 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1439 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1440 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, True, False, 0, 0)
1441 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1443 # iwconfig command
1444 self.iwconfig_cmd = gtk.Entry()
1445 self.iwconfig_cmd.set_width_chars(32)
1446 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1447 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1448 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1449 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1450 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, True, False, 5, 0)
1451 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, True, False, 0, 0)
1452 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, False, False, 0, 0)
1454 # iwlist command
1455 self.iwlist_cmd = gtk.Entry()
1456 self.iwlist_cmd.set_width_chars(32)
1457 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1458 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1459 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1460 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1461 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, True, False, 5, 0)
1462 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, True, False, 0, 0)
1463 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, False, False, 0, 0)
1465 # route command
1466 self.route_cmd = gtk.Entry()
1467 self.route_cmd.set_width_chars(32)
1468 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1469 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1470 self.route_cmd_label = gtk.Label("Network route configure command")
1471 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1472 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, True, False, 5, 0)
1473 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, True, False, 0, 0)
1474 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, False, False, 0, 0)
1476 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1477 ### End of Advanced tab
1479 ### DHCP tab
1480 # table to use for layout of DHCP prefs
1481 self.dhcp_table = gtk.Table()
1483 self.dhcp_cmd = gtk.Entry()
1484 self.dhcp_cmd.set_width_chars(32)
1485 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1486 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1487 self.dhcp_cmd_label = gtk.Label("Command")
1488 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1489 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1490 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1491 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1493 self.dhcp_args = gtk.Entry()
1494 self.dhcp_args.set_width_chars(32)
1495 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1496 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1497 self.dhcp_args_label = gtk.Label("Arguments")
1498 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1499 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1501 self.dhcp_kill_args = gtk.Entry()
1502 self.dhcp_kill_args.set_width_chars(32)
1503 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1504 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1505 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1506 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1507 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1509 self.dhcp_timeout = gtk.Entry()
1510 self.dhcp_timeout.set_width_chars(32)
1511 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1512 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1513 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1514 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1515 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1517 self.dhcp_pidfile = gtk.Entry()
1518 self.dhcp_pidfile.set_width_chars(32)
1519 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1520 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1521 self.dhcp_pidfile_label = gtk.Label("PID file")
1522 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1523 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1525 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1526 ### End of DHCP tab
1528 ### WPA tab
1529 # table to use for layout of DHCP prefs
1530 self.wpa_table = gtk.Table()
1532 self.wpa_cmd = gtk.Entry()
1533 self.wpa_cmd.set_width_chars(32)
1534 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1535 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1536 self.wpa_cmd_label = gtk.Label("Command")
1537 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1538 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1539 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1540 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1542 self.wpa_args = gtk.Entry()
1543 self.wpa_args.set_width_chars(32)
1544 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1545 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1546 self.wpa_args_label = gtk.Label("Arguments")
1547 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1548 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1550 self.wpa_kill_args = gtk.Entry()
1551 self.wpa_kill_args.set_width_chars(32)
1552 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1553 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1554 self.wpa_kill_args_label = gtk.Label("Kill command")
1555 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1556 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1558 self.wpa_config = gtk.Entry()
1559 self.wpa_config.set_width_chars(32)
1560 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1561 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1562 self.wpa_config_label = gtk.Label("Configuration file")
1563 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1564 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1566 self.wpa_driver = gtk.Entry()
1567 self.wpa_driver.set_width_chars(32)
1568 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1569 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1570 self.wpa_driver_label = gtk.Label("Driver")
1571 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1572 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1574 self.wpa_pidfile = gtk.Entry()
1575 self.wpa_pidfile.set_width_chars(32)
1576 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1577 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1578 self.wpa_pidfile_label = gtk.Label("PID file")
1579 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1580 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1582 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1583 ### End of WPA tab
1585 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1587 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1589 #Parameters:
1591 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1593 # 'data' -- tuple - list of arbitrary arguments (not used)
1595 #Returns:
1597 # nothing
1598 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1599 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1601 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1603 #Parameters:
1605 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1607 # 'data' -- tuple - list of arbitrary arguments (not used)
1609 #Returns:
1611 # nothing
1612 def toggle_speak(self, speak_toggle, data=None):
1613 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1614 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1616 # Display preferences dialog and operate until canceled or okayed.
1618 #Parameters:
1620 # nothing
1622 #Returns:
1624 # integer -- gtk response ID
1625 def run(self):
1626 self.dialog.show_all()
1627 return self.dialog.run()
1629 # Write updated values to config file.
1631 #Parameters:
1633 # nothing
1635 #Returns:
1637 # nothing
1638 def save(self):
1639 if self.w_auto_detect.get_active():
1640 set_network_device("auto_detect")
1641 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1642 else:
1643 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1644 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1645 self.confFile.set_opt('DEFAULT.interface', interface)
1646 set_network_device(interface)
1647 self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1648 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1649 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1650 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1651 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1652 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1653 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1654 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1655 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1656 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1657 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1658 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1659 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1660 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1661 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1662 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1663 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1664 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1665 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1666 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1667 self.confFile.write()
1669 # Remove preferences window.
1671 #Parameters:
1673 # nothing
1675 #Returns:
1677 # nothing
1678 def destroy(self):
1679 self.dialog.destroy()
1680 del self.dialog
1683 # Edit and return an AP profile.
1684 class profile_dialog:
1685 # Create a new profile_dialog.
1687 #Parameters:
1689 # 'parent' -- gtk.Object - Usually, the calling window.
1691 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1693 #Returns:
1695 # profile_dialog instance
1696 def __init__( self, parent, profile ):
1697 global wifi_radar_icon
1698 self.parent = parent
1699 self.profile = profile
1700 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1701 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1702 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1703 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1704 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1705 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1706 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1707 self.dialog.set_icon( icon )
1708 self.dialog.set_resizable( False )
1709 self.dialog.set_transient_for( self.parent.window )
1710 #self.dialog.set_size_request( 400, 400 )
1711 #################
1712 essid_table = gtk.Table( 1, 2, False )
1713 essid_table.set_row_spacings( 3 )
1714 essid_table.set_col_spacings( 3 )
1716 # The essid labels
1717 essid_table.attach( gtk.Label( 'Network Name' ), 0, 1, 0, 1 )
1718 # The essid textboxes
1719 self.essid_entry = gtk.Entry( 32 )
1720 self.essid_entry.set_text( self.profile['essid'] )
1721 essid_table.attach( self.essid_entry, 1, 2, 0, 1 )
1722 # Add the essid table to the dialog
1723 self.dialog.vbox.pack_start( essid_table, True, True, 5 )
1725 bssid_table = gtk.Table( 1, 2, False )
1726 bssid_table.set_row_spacings( 3 )
1727 bssid_table.set_col_spacings( 3 )
1728 # The bssid labels
1729 bssid_table.attach( gtk.Label( 'Network bssid' ), 0, 1, 0, 1 )
1730 # The bssid textboxes
1731 self.bssid_entry = gtk.Entry( 32 )
1732 self.bssid_entry.set_text( self.profile['bssid'] )
1733 bssid_table.attach( self.bssid_entry, 1, 2, 0, 1 )
1734 #self.key = gtk.Entry( 32 )
1735 #bssid_table.attach( self.key, 1, 2, 1, 2 )
1736 # Add the bssid table to the dialog
1737 self.dialog.vbox.pack_start( bssid_table, True, True, 5 )
1739 # create the wifi expander
1740 self.wifi_expander = gtk.Expander( WIFI_SET_LABEL )
1741 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1742 wifi_table = gtk.Table( 4, 2, False )
1743 wifi_table.set_row_spacings( 3 )
1744 wifi_table.set_col_spacings( 3 )
1745 # The Wifi labels
1746 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1747 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1748 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1749 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1750 # The Wifi text boxes
1751 self.mode_combo = gtk.combo_box_new_text()
1752 for mode in self.WIFI_MODES:
1753 self.mode_combo.append_text( mode )
1754 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1755 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1756 self.channel_combo = gtk.combo_box_new_text()
1757 for channel in self.WIFI_CHANNELS:
1758 self.channel_combo.append_text( channel )
1759 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
1760 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
1762 self.key_entry = gtk.Entry( 64 )
1763 self.key_entry.set_text( self.profile['key'] )
1764 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
1766 self.security_combo = gtk.combo_box_new_text()
1767 for security in self.WIFI_SECURITY:
1768 self.security_combo.append_text( security )
1769 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
1770 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
1771 # Add the wifi table to the expander
1772 self.wifi_expander.add( wifi_table )
1773 # Add the expander to the dialog
1774 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
1776 # create the wpa expander
1777 self.wpa_expander = gtk.Expander( NO_WPA_LABEL )
1778 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
1779 wpa_table = gtk.Table( 1, 2, False )
1780 wpa_table.set_row_spacings( 3 )
1781 wpa_table.set_col_spacings( 3 )
1782 # The labels
1783 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
1784 # The text boxes
1785 self.wpa_driver_entry = gtk.Entry()
1786 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
1787 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
1788 # Add the wpa table to the expander
1789 self.wpa_expander.add( wpa_table )
1790 # Add the expander to the dialog
1791 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
1793 # create the dhcp expander
1794 self.dhcp_expander = gtk.Expander( USE_DHCP_LABEL )
1795 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1796 ip_table = gtk.Table( 6, 2, False )
1797 ip_table.set_row_spacings( 3 )
1798 ip_table.set_col_spacings( 3 )
1799 # The IP labels
1800 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
1801 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
1802 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
1803 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
1804 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
1805 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
1806 # The IP text boxes
1807 self.ip_entry = gtk.Entry( 15 )
1808 self.ip_entry.set_text( self.profile['ip'] )
1809 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
1810 self.netmask_entry = gtk.Entry( 15 )
1811 self.netmask_entry.set_text( self.profile['netmask'] )
1812 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
1813 self.gw_entry = gtk.Entry( 15 )
1814 self.gw_entry.set_text( self.profile['gateway'] )
1815 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
1816 self.domain_entry = gtk.Entry( 32 )
1817 self.domain_entry.set_text( self.profile['domain'] )
1818 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
1819 self.dns1_entry = gtk.Entry( 15 )
1820 self.dns1_entry.set_text( self.profile['dns1'] )
1821 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
1822 self.dns2_entry = gtk.Entry( 15 )
1823 self.dns2_entry.set_text( self.profile['dns2'] )
1824 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
1825 # Add the ip table to the expander
1826 self.dhcp_expander.add( ip_table )
1827 # Add the expander to the dialog
1828 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
1830 # create the connection-building postpre expander
1831 self.con_pp_expander = gtk.Expander( CON_PP_LABEL )
1832 con_pp_table = gtk.Table( 2, 2, False )
1833 con_pp_table.set_row_spacings( 3 )
1834 con_pp_table.set_col_spacings( 3 )
1835 # The labels
1836 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1837 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1838 # The text boxes
1839 self.con_prescript_entry = gtk.Entry()
1840 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
1841 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
1842 self.con_postscript_entry = gtk.Entry()
1843 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
1844 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
1845 # Add the pp table to the expander
1846 self.con_pp_expander.add( con_pp_table )
1847 # Add the expander to the dialog
1848 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
1850 # create the disconnection postpre expander
1851 self.dis_pp_expander = gtk.Expander( DIS_PP_LABEL )
1852 dis_pp_table = gtk.Table( 2, 2, False )
1853 dis_pp_table.set_row_spacings( 3 )
1854 dis_pp_table.set_col_spacings( 3 )
1855 # The labels
1856 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
1857 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
1858 # The text boxes
1859 self.dis_prescript_entry = gtk.Entry()
1860 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
1861 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
1862 self.dis_postscript_entry = gtk.Entry()
1863 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
1864 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
1865 # Add the pp table to the expander
1866 self.dis_pp_expander.add( dis_pp_table )
1867 # Add the expander to the dialog
1868 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
1870 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
1872 #Parameters:
1874 # nothing
1876 #Returns:
1878 # dictionary or None -- a profile, or None on cancel
1879 def run( self ):
1880 self.dialog.show_all()
1881 if self.dialog.run():
1882 self.profile['known'] = True
1883 self.profile['essid'] = self.essid_entry.get_text().strip()
1884 self.profile['bssid'] = self.bssid_entry.get_text().strip()
1885 self.profile['key'] = self.key_entry.get_text().strip()
1886 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
1887 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
1888 self.profile['encrypted'] = ( self.profile['security'] != '' )
1889 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
1890 self.profile['protocol'] = 'g'
1891 self.profile['available'] = ( self.profile['signal'] > 0 )
1892 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
1893 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
1894 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
1895 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
1896 # wpa
1897 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
1898 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
1899 # dhcp
1900 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
1901 self.profile['ip'] = self.ip_entry.get_text().strip()
1902 self.profile['netmask'] = self.netmask_entry.get_text().strip()
1903 self.profile['gateway'] = self.gw_entry.get_text().strip()
1904 self.profile['domain'] = self.domain_entry.get_text().strip()
1905 self.profile['dns1'] = self.dns1_entry.get_text().strip()
1906 self.profile['dns2'] = self.dns2_entry.get_text().strip()
1907 return self.profile
1908 return None
1910 # Remove profile dialog.
1912 #Parameters:
1914 # nothing
1916 #Returns:
1918 # nothing
1919 def destroy( self ):
1920 self.dialog.destroy()
1921 del self.dialog
1923 # Respond to expanding/hiding IP segment.
1925 #Parameters:
1927 # 'widget' -- gtk.Widget - The widget sending the event.
1929 # 'data' -- tuple - List of arbitrary arguments (not used)
1931 #Returns:
1933 # nothing
1934 def toggle_use_dhcp( self, widget, data = None ):
1935 expanded = self.dhcp_expander.get_expanded()
1936 if expanded:
1937 self.dhcp_expander.set_label( USE_IP_LABEL )
1938 else:
1939 self.dhcp_expander.set_label( USE_DHCP_LABEL )
1941 # Respond to expanding/hiding WPA segment.
1943 #Parameters:
1945 # 'widget' -- gtk.Widget - The widget sending the event.
1947 # 'data' -- tuple - List of arbitrary arguments (not used)
1949 #Returns:
1951 # nothing
1952 def toggle_use_wpa( self, widget, data = None ):
1953 expanded = self.wpa_expander.get_expanded()
1954 if expanded:
1955 self.wpa_expander.set_label( USE_WPA_LABEL )
1956 else:
1957 self.wpa_expander.set_label( NO_WPA_LABEL )
1959 # Return the index where item matches a cell in array.
1961 #Parameters:
1963 # 'item' -- string - Item to find in array
1965 # 'array' -- list - List in which to find match.
1967 #Returns:
1969 # integer - 0 (no match) or higher (index of match)
1970 def get_array_index( self, item, array ):
1971 try:
1972 return array.index( item.strip() )
1973 except:
1974 pass
1975 return 0
1977 # Return the value in array[ index ]
1979 #Parameters:
1981 # 'index' -- integer - The index to look up.
1983 # 'array' -- list - List in which to look up value.
1985 #Returns:
1987 # string -- empty string (no match) or looked up value
1988 def get_array_item( self, index, array ):
1989 try:
1990 return array[ index ]
1991 except:
1992 pass
1993 return ''
1995 # A simple class for putting up a "Please wait" dialog so the user
1996 # doesn't think we've forgotten about them. Implements the status interface.
1997 class StatusWindow:
1998 # Create a new StatusWindow.
2000 #Parameters:
2002 # 'parent' -- gtk.Object - Usually, the calling window.
2004 #Returns:
2006 # StatusWindow instance
2008 #NOTE:
2010 # Sample implementation of status interface. Status interface
2011 #requires .show(), .update_message(message), and .hide() methods.
2012 def __init__( self, parent ):
2013 global wifi_radar_icon
2014 self.parent = parent
2015 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2016 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2017 self.dialog.set_icon( icon )
2018 self.lbl = gtk.Label("Please wait...")
2019 self.bar = gtk.ProgressBar()
2020 self.dialog.vbox.pack_start(self.lbl)
2021 self.dialog.vbox.pack_start(self.bar)
2022 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2023 self.timer = None
2025 # Change the message displayed to the user.
2027 #Parameters:
2029 # 'message' -- string - The message to show to the user.
2031 #Returns:
2033 # nothing
2034 def update_message( self, message ):
2035 self.lbl.set_text(message)
2037 # Update the StatusWindow progress bar.
2039 #Parameters:
2041 # nothing
2043 #Returns:
2045 # True -- always return True
2046 def update_window( self ):
2047 self.bar.pulse()
2048 return True
2050 # Display and operate the StatusWindow.
2052 #Parameters:
2054 # nothing
2056 #Returns:
2058 # nothing
2059 def run( self ):
2060 pass
2062 # Show all the widgets of the StatusWindow.
2064 #Parameters:
2066 # nothing
2068 #Returns:
2070 # nothing
2071 def show( self ):
2072 self.dialog.show_all()
2073 self.timer = gobject.timeout_add(250, self.update_window)
2074 return False
2076 # Hide all the widgets of the StatusWindow.
2078 #Parameters:
2080 # nothing
2082 #Returns:
2084 # nothing
2085 def hide( self ):
2086 if self.timer:
2087 gobject.source_remove(self.timer)
2088 self.timer = None
2089 self.dialog.hide_all()
2090 return False
2092 # Remove the StatusWindow.
2094 #Parameters:
2096 # nothing
2098 #Returns:
2100 # nothing
2101 def destroy( self ):
2102 if self.timer:
2103 gobject.source_remove(self.timer)
2104 self.dialog.destroy()
2105 del self.dialog
2108 # Manage a GTK About Dialog
2109 class about_dialog(gtk.AboutDialog):
2110 # Subclass GTK AboutDialog
2112 #Parameters:
2114 # nothing
2116 #Returns:
2118 # nothing
2119 def __init__( self ):
2120 global wifi_radar_icon
2122 gtk.AboutDialog.__init__(self)
2123 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"])
2124 self.set_comments("WiFi connection manager")
2125 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2126 self.set_documenters(["Gary Case"])
2127 license = """
2128 This program is free software; you can redistribute it and/or modify
2129 it under the terms of the GNU General Public License as published by
2130 the Free Software Foundation; either version 2 of the License, or
2131 (at your option) any later version.
2133 This program is distributed in the hope that it will be useful,
2134 but WITHOUT ANY WARRANTY; without even the implied warranty of
2135 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2136 GNU General Public License for more details.
2138 You should have received a copy of the GNU General Public License
2139 along with this program; if not, write to the Free Software
2140 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2141 self.set_license(license)
2142 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2143 self.set_logo(logo)
2144 self.set_name("WiFi Radar")
2145 self.set_version(WIFI_RADAR_VERSION)
2146 self.set_website("http://wifi-radar.berlios.de")
2150 # Manage the configuration for the application, including reading and writing the config from/to a file.
2151 class ConfigFile(ConfigParser.SafeConfigParser):
2152 # Create a new ConfigFile.
2154 #Parameters:
2156 # 'filename' -- string - The configuration file's name.
2158 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2160 #Returns:
2162 # ConfigFile instance
2163 def __init__( self, filename, defaults, raw=False ):
2164 self.filename = filename
2165 self.raw = raw
2166 self.auto_profile_order = []
2167 ConfigParser.SafeConfigParser.__init__(self, defaults)
2169 # Set the contents of a section to values from a dictionary.
2171 #Parameters:
2173 # 'section_name' -- string - Configuration file section.
2175 # 'section_dict' -- dictionary - Values to add to section.
2177 #Returns:
2179 # nothing
2180 def set_section( self, section_name, section_dict ):
2181 try:
2182 self.add_section(section_name)
2183 except ConfigParser.DuplicateSectionError:
2184 pass
2185 for key in section_dict.keys():
2186 if type(section_dict[key]) == BooleanType:
2187 self.set_bool_opt(section_name + "." + key, section_dict[key])
2188 elif type(section_dict[key]) == IntType:
2189 self.set_int_opt(section_name + "." + key, section_dict[key])
2190 elif type(section_dict[key]) == FloatType:
2191 self.set_float_opt(section_name + "." + key, section_dict[key])
2192 else:
2193 self.set_opt(section_name + "." + key, section_dict[key])
2195 # Return the profile recorded in the specified section.
2197 #Parameters:
2199 # 'section_name' -- string - Configuration file section.
2201 #Returns:
2203 # dictionary or None - The specified profile or None if not found
2204 def get_profile( self, section_name ):
2205 if section_name in self.profiles():
2206 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' ]
2207 bool_types = [ 'known', 'available', 'encrypted', 'use_wpa', 'use_dhcp' ]
2208 int_types = [ 'signal' ]
2209 profile = {}
2210 for option in bool_types:
2211 profile[option] = self.get_opt_as_bool( section_name + "." + option )
2212 for option in int_types:
2213 profile[option] = self.get_opt_as_int( section_name + "." + option )
2214 for option in str_types:
2215 profile[option] = self.get_opt( section_name + "." + option )
2216 return profile
2217 return None
2219 # Get a config option and handle exceptions.
2221 #Parameters:
2223 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2224 # period and the option key. (E.g. "DEFAULT.interface")
2226 #Returns:
2228 # string or None - option value as string or None on failure
2229 def get_opt( self, option_path ):
2230 #print "ConfigFile.get_opt: ", option_path
2231 (section, option) = option_path.split('.')
2232 try:
2233 return self.get(section, option, self.raw)
2234 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2235 return None
2237 # Get a config option and return as a boolean type.
2239 #Parameters:
2241 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2242 # period and the option key. (E.g. "DEFAULT.interface")
2244 #Returns:
2246 # boolean - option value as boolean
2247 def get_opt_as_bool( self, option_path ):
2248 option = self.get_opt(option_path)
2249 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2250 return option
2251 if option == 'True':
2252 return True
2253 if option == 'False':
2254 return False
2255 raise ValueError, 'boolean option was not True or False'
2257 # Get a config option and return as an integer type.
2259 #Parameters:
2261 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2262 # period and the option key. (E.g. "DEFAULT.interface")
2264 #Returns:
2266 # integer- option value as integer
2267 def get_opt_as_int( self, option_path ):
2268 return int(float(self.get_opt(option_path)))
2270 # Convert boolean type to string and set config option.
2272 #Parameters:
2274 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2275 # period and the option key. (E.g. "DEFAULT.interface")
2277 # 'value' -- boolean - Value to set.
2279 #Returns:
2281 # nothing
2282 def set_bool_opt( self, option_path, value ):
2283 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2284 value == 'True'
2285 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2286 value == 'False'
2287 else:
2288 raise ValueError, 'cannot convert value to string'
2289 self.set_opt(option_path, repr(value))
2291 # Convert integer type to string and set config option.
2293 #Parameters:
2295 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2296 # period and the option key. (E.g. "DEFAULT.interface")
2298 # 'value' -- integer - Value to set.
2300 #Returns:
2302 # nothing
2303 def set_int_opt( self, option_path, value ):
2304 if not isinstance(value, IntType):
2305 raise ValueError, 'value is not an integer'
2306 self.set_opt(option_path, repr(value))
2308 # Convert float type to string and set config option.
2310 #Parameters:
2312 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2313 # period and the option key. (E.g. "DEFAULT.interface")
2315 # 'value' -- float - Value to set.
2317 #Returns:
2319 # nothing
2320 def set_float_opt( self, option_path, value ):
2321 if not isinstance(value, FloatType):
2322 raise ValueError, 'value is not a float'
2323 self.set_opt(option_path, repr(int(value)))
2325 # Set a config option while handling exceptions.
2327 #Parameters:
2329 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2330 # period and the option key. (E.g. "DEFAULT.interface")
2332 # 'value' -- string - Value to set.
2334 #Returns:
2336 # nothing
2337 def set_opt( self, option_path, value ):
2338 (section, option) = option_path.split('.')
2339 try:
2340 self.set(section, option, value)
2341 except ConfigParser.NoSectionError:
2342 self.add_section(section)
2343 self.set_opt(option_path, value)
2345 # Return a list of the section names which denote AP profiles.
2347 #Parameters:
2349 # nothing
2351 #Returns:
2353 # list - profile names
2354 def profiles( self ):
2355 profile_list = []
2356 for section in self.sections():
2357 if ':' in section:
2358 profile_list.append(section)
2359 return profile_list
2361 # Read configuration file from disk into instance variables.
2363 #Parameters:
2365 # nothing
2367 #Returns:
2369 # nothing
2370 def read( self ):
2371 fp = open( self.filename, "r" )
2372 self.readfp(fp)
2373 # convert the auto_profile_order to a list for ordering
2374 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2375 for ap in self.profiles():
2376 self.set_bool_opt( ap + '.known', True)
2377 if ap in self.auto_profile_order: continue
2378 self.auto_profile_order.append( ap )
2379 fp.close()
2381 # Write configuration file to disk from instance variables. Copied from
2382 # ConfigParser and modified to write options in alphabetical order.
2384 #Parameters:
2386 # nothing
2388 #Returns:
2390 # nothing
2391 def write( self ):
2392 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2393 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2394 fp = open( self.filename, "w" )
2395 # write DEFAULT section first
2396 if self._defaults:
2397 fp.write("[DEFAULT]\n")
2398 for key in sorted(self._defaults.keys()):
2399 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2400 fp.write("\n")
2401 # write non-profile sections first
2402 for section in self._sections:
2403 if section not in self.profiles():
2404 fp.write("[%s]\n" % section)
2405 for key in sorted(self._sections[section].keys()):
2406 if key != "__name__":
2407 fp.write("%s = %s\n" %
2408 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2409 fp.write("\n")
2410 # write profile sections
2411 for section in self._sections:
2412 if section in self.profiles():
2413 fp.write("[%s]\n" % section)
2414 for key in sorted(self._sections[section].keys()):
2415 if key != "__name__":
2416 fp.write("%s = %s\n" %
2417 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2418 fp.write("\n")
2419 fp.close()
2423 # Load our conf file and known profiles
2424 # Defaults, these may get overridden by values found in the conf file.
2425 config_defaults = { # The network interface you use.
2426 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2427 'interface': "auto_detect",
2428 # How long should the scan for access points last?
2429 'scan_timeout': '5',
2430 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2431 # Set the speak_up option to false if you do not have or want this.
2432 'speak_command': '/usr/bin/say',
2433 # Should I speak up when connecting to a network? (If you have a speech command)
2434 'speak_up': 'False',
2435 # You may set this to true for cards that require a "commit" command with iwconfig
2436 'commit_required': 'False',
2437 # You may set this to true for cards that require the interface to be brought up first
2438 'ifup_required': 'False',
2439 # Set the location of several important programs
2440 'iwlist_command': '/sbin/iwlist',
2441 'iwconfig_command': '/sbin/iwconfig',
2442 'ifconfig_command': '/sbin/ifconfig',
2443 'route_command': '/sbin/route',
2444 'auto_profile_order': '[]',
2445 'version': WIFI_RADAR_VERSION }
2447 config_dhcp = { # DHCP client
2448 'command': 'dhcpcd',
2449 # How long to wait for an IP addr from DHCP server
2450 'timeout': '30',
2451 # Arguments to use with DHCP client on connect
2452 'args': '-D -o -i dhcp_client -t %(timeout)s',
2453 # Argument to use with DHCP client on disconnect
2454 'kill_args': '-k',
2455 # The file where DHCP client PID is written
2456 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2458 config_wpa = { # WPA Supplicant
2459 'command': '/usr/sbin/wpa_supplicant',
2460 # Arguments to use with WPA Supplicant on connect
2461 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2462 # Arguments to use with WPA Supplicant on disconnect
2463 'kill_command': '',
2464 # Where the WPA Supplicant config file can be found
2465 'configuration': '/etc/wpa_supplicant.conf',
2466 # Driver to use with WPA Supplicant
2467 'driver': 'wext',
2468 # The file where WPA Supplicant PID is written
2469 'pidfile': '/var/run/wpa_supplicant.pid' }
2471 # initialize config, with defaults
2472 confFile = ConfigFile(CONF_FILE, config_defaults)
2473 confFile.set_section("DHCP", config_dhcp)
2474 confFile.set_section("WPA", config_wpa)
2476 if not os.path.isfile( CONF_FILE ):
2477 confFile.set_bool_opt('DEFAULT.new_file', True)
2478 else:
2479 if not os.access(CONF_FILE, os.R_OK):
2480 print "Can't open " + CONF_FILE + "."
2481 print "Are you root?"
2482 sys.exit()
2483 confFile.read()
2486 ####################################################################################################
2487 # Embedded Images
2488 wifi_radar_icon = [ ""
2489 "GdkP"
2490 "\0\0\22""7"
2491 "\2\1\0\2"
2492 "\0\0\1\214"
2493 "\0\0\0c"
2494 "\0\0\0O"
2495 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2496 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2497 "\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"
2498 "\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"
2499 "\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"
2500 "\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"
2501 "\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"
2502 "\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"
2503 "\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"
2504 "\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"
2505 "\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"
2506 "\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"
2507 "\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"
2508 "\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"
2509 "\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"
2510 "\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"
2511 "\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"
2512 "\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"
2513 "\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"
2514 "\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"
2515 "\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"
2516 "\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"
2517 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2518 "\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"
2519 "\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"
2520 "\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"
2521 "\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"
2522 "\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"
2523 "\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"
2524 "\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"
2525 "\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"
2526 "\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"
2527 "\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"
2528 "\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"
2529 "\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"
2530 "\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"
2531 "\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"
2532 "\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"
2533 "\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"
2534 "\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"
2535 "\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"
2536 "\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"
2537 "\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"
2538 "\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"
2539 "\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"
2540 "\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"
2541 "\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"
2542 "\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"
2543 "\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"
2544 "\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"
2545 "\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"
2546 "\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"
2547 "\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"
2548 "\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"
2549 "\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"
2550 "\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"
2551 "\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"
2552 "\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"
2553 "\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"
2554 "\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"
2555 "\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"
2556 "\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"
2557 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2558 "\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"
2559 "\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"
2560 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2561 "\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"
2562 "\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"
2563 "\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"
2564 "\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"
2565 "\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"
2566 "\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"
2567 "\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"
2568 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2569 "\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"
2570 "\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"
2571 "\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"
2572 "\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"
2573 "\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"
2574 "\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"
2575 "|\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"
2576 "\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"
2577 "\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"
2578 "\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"
2579 "\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"
2580 "\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"
2581 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2582 "\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"
2583 "\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"
2584 "\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"
2585 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2586 "\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"
2587 "\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"
2588 "\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"
2589 "\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"
2590 "\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"
2591 "\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"
2592 "\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"
2593 "\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"
2594 "\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"
2595 "\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"
2596 "\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"
2597 "\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"
2598 "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"
2599 "\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"
2600 "\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"
2601 "\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"
2602 "\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"
2603 "\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"
2604 "\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"
2605 "\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"
2606 "\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"
2607 "\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"
2608 "\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"
2609 "\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"
2610 "\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"
2611 "\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"
2612 "\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"
2613 "\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|"
2614 "\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"
2615 "\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"
2616 "\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"
2617 "\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"
2618 "\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"
2619 "\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"
2620 "\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"
2621 "\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"
2622 "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"
2623 "\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"
2624 "\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"
2625 "\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"
2626 "\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"
2627 "\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"
2628 "\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"
2629 "\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"
2630 "\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"
2631 "\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"
2632 "\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"
2633 "\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"
2634 "\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"
2635 "\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"
2636 "\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"
2637 "\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"
2638 "\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"
2639 "\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"
2640 "\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"
2641 "\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"
2642 "\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"
2643 "\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"
2644 "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"
2645 "\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"
2646 "\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"
2647 "\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"
2648 "\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"
2649 "\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"
2650 "\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"
2651 "\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"
2652 "\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"
2653 "\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"
2654 "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"
2655 "\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"
2656 "\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"
2657 "\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"
2658 "\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"
2659 "\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"
2660 "\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"
2661 "\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"
2662 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2663 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2664 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2665 "\0"]
2667 known_profile_icon = [ ""
2668 "GdkP"
2669 "\0\0\5""0"
2670 "\2\1\0\2"
2671 "\0\0\0P"
2672 "\0\0\0\24"
2673 "\0\0\0\24"
2674 "\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"
2675 "\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"
2676 "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"
2677 "\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"
2678 "\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"
2679 "\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"
2680 "\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"
2681 "\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"
2682 "\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"
2683 "\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"
2684 "\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"
2685 "\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"
2686 "\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"
2687 "\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"
2688 "\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"
2689 "\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"
2690 "\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"
2691 "\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"
2692 "\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"
2693 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2694 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2695 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2696 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2697 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2698 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2699 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2700 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2701 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2702 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2703 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2704 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2705 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2706 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2707 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2708 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2709 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2710 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2711 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2712 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2713 "\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"
2714 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2715 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2716 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2717 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2718 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
2719 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
2720 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
2721 "\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"
2722 "\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"
2723 "\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"
2724 "\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"
2725 "\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"]
2727 unknown_profile_icon = [ ""
2728 "GdkP"
2729 "\0\0\5\22"
2730 "\2\1\0\2"
2731 "\0\0\0P"
2732 "\0\0\0\24"
2733 "\0\0\0\24"
2734 "\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"
2735 "\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"
2736 "\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"
2737 "\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"
2738 "(\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"
2739 "\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"
2740 "#\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"
2741 "\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"
2742 "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"
2743 "\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"
2744 "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"
2745 "\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"
2746 "\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"
2747 "\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"
2748 "\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"
2749 "\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"
2750 "\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"
2751 "\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"
2752 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
2753 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
2754 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
2755 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
2756 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
2757 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
2758 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
2759 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
2760 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
2761 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
2762 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
2763 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
2764 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
2765 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
2766 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
2767 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
2768 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
2769 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
2770 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
2771 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
2772 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
2773 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
2774 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
2775 "\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"
2776 "\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"
2777 "\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"
2778 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
2780 signal_xpm_barely = [
2781 "20 20 10 1",
2782 " c None",
2783 ". c #C6C6C6",
2784 "+ c #CCCCCC",
2785 "@ c #DBDBDB",
2786 "# c #D3D3D3",
2787 "$ c #A9B099",
2788 "% c #95A173",
2789 "& c #6B8428",
2790 "* c #B4B7AC",
2791 "= c #80924D",
2792 " .+++.",
2793 " +@@@+",
2794 " +@@@+",
2795 " +@@@+",
2796 " +@@@+",
2797 " .++++#@@@+",
2798 " +@@@@@@@@+",
2799 " +@@@@@@@@+",
2800 " +@@@@@@@@+",
2801 " +@@@@@@@@+",
2802 " $%%%%#@@@@@@@@+",
2803 " %&&&&@@@@@@@@@+",
2804 " %&&&&@@@@@@@@@+",
2805 " %&&&&@@@@@@@@@+",
2806 " %&&&&@@@@@@@@@+",
2807 "*%%%%=&&&&@@@@@@@@@+",
2808 "%&&&&&&&&&@@@@@@@@@+",
2809 "%&&&&&&&&&@@@@@@@@@+",
2810 "%&&&&&&&&&@@@@@@@@@+",
2811 "*%%%%%%%%%+++++++++."
2815 signal_xpm_best = [
2816 "20 20 6 1",
2817 " c None",
2818 ". c #9DAABF",
2819 "+ c #7B96BF",
2820 "@ c #386EBF",
2821 "# c #5982BF",
2822 "$ c #AEB4BF",
2823 " .+++.",
2824 " +@@@+",
2825 " +@@@+",
2826 " +@@@+",
2827 " +@@@+",
2828 " .++++#@@@+",
2829 " +@@@@@@@@+",
2830 " +@@@@@@@@+",
2831 " +@@@@@@@@+",
2832 " +@@@@@@@@+",
2833 " .++++#@@@@@@@@+",
2834 " +@@@@@@@@@@@@@+",
2835 " +@@@@@@@@@@@@@+",
2836 " +@@@@@@@@@@@@@+",
2837 " +@@@@@@@@@@@@@+",
2838 "$++++#@@@@@@@@@@@@@+",
2839 "+@@@@@@@@@@@@@@@@@@+",
2840 "+@@@@@@@@@@@@@@@@@@+",
2841 "+@@@@@@@@@@@@@@@@@@+",
2842 "$++++++++++++++++++."
2845 signal_xpm_none = [
2846 "20 20 6 1",
2847 " c None",
2848 ". c #C6C6C6",
2849 "+ c #CCCCCC",
2850 "@ c #DBDBDB",
2851 "# c #D3D3D3",
2852 "$ c #C2C2C2",
2853 " .+++.",
2854 " +@@@+",
2855 " +@@@+",
2856 " +@@@+",
2857 " +@@@+",
2858 " .++++#@@@+",
2859 " +@@@@@@@@+",
2860 " +@@@@@@@@+",
2861 " +@@@@@@@@+",
2862 " +@@@@@@@@+",
2863 " .++++#@@@@@@@@+",
2864 " +@@@@@@@@@@@@@+",
2865 " +@@@@@@@@@@@@@+",
2866 " +@@@@@@@@@@@@@+",
2867 " +@@@@@@@@@@@@@+",
2868 "$++++#@@@@@@@@@@@@@+",
2869 "+@@@@@@@@@@@@@@@@@@+",
2870 "+@@@@@@@@@@@@@@@@@@+",
2871 "+@@@@@@@@@@@@@@@@@@+",
2872 "$++++++++++++++++++."
2875 signal_xpm_ok = [
2876 "20 20 10 1",
2877 " c None",
2878 ". c #C6C6C6",
2879 "+ c #CCCCCC",
2880 "@ c #DBDBDB",
2881 "# c #A1A5B2",
2882 "$ c #848DA5",
2883 "% c #D3D3D3",
2884 "& c #4A5B8C",
2885 "* c #677498",
2886 "= c #B0B2B8",
2887 " .+++.",
2888 " +@@@+",
2889 " +@@@+",
2890 " +@@@+",
2891 " +@@@+",
2892 " #$$$$%@@@+",
2893 " $&&&&@@@@+",
2894 " $&&&&@@@@+",
2895 " $&&&&@@@@+",
2896 " $&&&&@@@@+",
2897 " #$$$$*&&&&@@@@+",
2898 " $&&&&&&&&&@@@@+",
2899 " $&&&&&&&&&@@@@+",
2900 " $&&&&&&&&&@@@@+",
2901 " $&&&&&&&&&@@@@+",
2902 "=$$$$*&&&&&&&&&@@@@+",
2903 "$&&&&&&&&&&&&&&@@@@+",
2904 "$&&&&&&&&&&&&&&@@@@+",
2905 "$&&&&&&&&&&&&&&@@@@+",
2906 "=$$$$$$$$$$$$$$++++."
2910 signal_xpm_low = [
2911 "20 20 8 1",
2912 " c None",
2913 ". c #C6C6C6",
2914 "+ c #CCCCCC",
2915 "@ c #DBDBDB",
2916 "# c #D3D3D3",
2917 "$ c #BFB0B5",
2918 "% c #C18799",
2919 "& c #C54F74",
2920 " .+++.",
2921 " +@@@+",
2922 " +@@@+",
2923 " +@@@+",
2924 " +@@@+",
2925 " .++++#@@@+",
2926 " +@@@@@@@@+",
2927 " +@@@@@@@@+",
2928 " +@@@@@@@@+",
2929 " +@@@@@@@@+",
2930 " .++++#@@@@@@@@+",
2931 " +@@@@@@@@@@@@@+",
2932 " +@@@@@@@@@@@@@+",
2933 " +@@@@@@@@@@@@@+",
2934 " +@@@@@@@@@@@@@+",
2935 "$%%%%#@@@@@@@@@@@@@+",
2936 "%&&&&@@@@@@@@@@@@@@+",
2937 "%&&&&@@@@@@@@@@@@@@+",
2938 "%&&&&@@@@@@@@@@@@@@+",
2939 "$%%%%++++++++++++++."
2943 ####################################################################################################
2944 # Make so we can be imported
2945 if __name__ == "__main__":
2946 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
2947 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
2948 else:
2949 import gtk, gobject
2950 gtk.gdk.threads_init()
2951 apQueue = Queue.Queue(100)
2952 commQueue = Queue.Queue(2)
2953 threading.Thread( None, scanning_thread, None, ( confFile, apQueue, commQueue ) ).start()
2954 main_radar_window = radar_window(confFile, apQueue, commQueue)
2955 gobject.timeout_add( 500, main_radar_window.update_plist_items )
2956 main_radar_window.main()