Move button state update to radar_window.update_connect_buttons()
[wifi-radar.git] / wifi-radar
blob515b410fd6136a733f01e2e9a49e357523be4a31
1 #!/usr/bin/python
3 # A wireless profile manager for Linux
5 # Created by:
6 # Ahmad Baitalmal <ahmad@baitalmal.com>
8 # Maintained 2006-2009 by:
9 # Brian Elliott Finley <brian@thefinleys.com>
11 # Maintained by:
12 # Sean Robinson <seankrobinson@gmail.com>
14 # License:
15 # GPL
17 # http://wifi-radar.berlios.de
19 # See CREDITS file for more contributors.
20 # See ChangeLog file for, well, changes.
22 # NOTE: Remove the '-OO' from '#!/usr/bin/python -OO' in the first line to
23 # turn on console debugging.
25 import ConfigParser
26 import errno
27 import gtk
28 import logging
29 import logging.handlers
30 import os
31 import Queue
32 import re
33 import string
34 import sys
35 import threading
36 from signal import SIGTERM
37 from subprocess import call, Popen, PIPE
38 from time import sleep
39 from types import *
41 WIFI_RADAR_VERSION = "0.0.0"
44 # Where the conf file should live could be different for your distro. Please change
45 # at install time with "make install sysconfdir=/etc/wifi-radar" or similar. -BEF-
47 CONF_FILE = "/etc/wifi-radar/wifi-radar.conf"
49 os.environ['LC_MESSAGES'] = 'C'
52 ####################################################################################################
53 ####################################################################################################
55 # Sets the interface to the specified network device
57 #Parameters:
59 # 'device' -- string - The network device to use
61 #Returns:
63 # nothing
64 def set_network_device( device ):
65 #print "set_network_device: ", device
66 if device != "auto_detect":
67 confFile.set_opt('DEFAULT.interface', device)
68 else: # auto detect network device
69 # Get a list of 802.11 enabled devices by parsing the output of iwconfig.
70 # If no devices are found, default to eth1.
71 # call iwconfig command and read output
72 iwconfig_info = Popen(confFile.get_opt('DEFAULT.iwconfig_command'), shell=True, stdout=PIPE).stdout
73 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if("ESSID" in x)]
74 if len(wireless_devices) > 0:
75 confFile.set_opt('DEFAULT.interface', wireless_devices[0])
76 #else:
77 #print "No wifi-device found. Exiting."
78 #sys.exit()
80 # Return a blank profile
82 #Parameters:
84 # none
86 #Returns:
88 # dictionary -- An AP profile with defaults set.
89 def get_new_profile():
90 return { 'known': False,
91 'available': False,
92 'encrypted': False,
93 'essid': '',
94 'bssid': '',
95 'roaming': False,
96 'protocol': 'g',
97 'signal': 0,
98 'channel': 'auto',
99 'con_prescript': '',
100 'con_postscript': '',
101 'dis_prescript': '',
102 'dis_postscript': '',
103 'key': '',
104 'mode': 'auto',
105 'security': '',
106 'use_wpa': False,
107 'wpa_driver': '',
108 'use_dhcp': True,
109 'ip': '',
110 'netmask': '',
111 'gateway': '',
112 'domain': '',
113 'dns1': '',
114 'dns2': ''
117 # Combine essid and bssid to make a config file section name
119 #Parameters:
121 # 'essid' -- string - AP ESSID
123 # 'bssid' -- string - AP BSSID
125 #Returns:
127 # string -- the bssid concatenated to a colon, concatenated to the essid
128 def make_section_name( essid, bssid ):
129 return essid + ':' + bssid
131 # Split a config file section name into an essid and a bssid
133 #Parameters:
135 # 'section' -- string - Config file section name
137 #Returns:
139 # list -- the essid and bssid
140 def split_section_name( section ):
141 parts = re.split(':', section)
142 return [ ':'.join(parts[0:len(parts)-6]), ':'.join(parts[len(parts)-6:len(parts)]) ]
144 # Run commands through the shell
146 #Parameters:
148 # 'command' -- tuple - The command and arguments to run.
150 # 'environment' -- dictionary - Environment variables (as keys) and their values.
152 #Returns:
154 # boolean -- True on success, otherwise, False
155 def shellcmd( command, environment = None ):
156 try:
157 env_tmp = os.environ
158 env_tmp.update(environment)
159 command = ' '.join(command)
160 return_code = call(command, shell=True, env=env_tmp)
161 if return_code >= 0:
162 return True
163 else:
164 print >>sys.stderr, "Child was terminated by signal", -return_code
165 except OSError, exception:
166 print >>sys.stderr, "Execution failed:", exception
167 return False
169 # Speak feedback message to user
171 #Parameters:
173 # 'words' -- string - Message to speak to user
175 #Returns:
177 # nothing
178 def say( words ):
179 if not confFile.get_opt_as_bool('DEFAULT.speak_up'): return
180 words = words.replace( "\"", "\\\"" )
181 shellcmd([confFile.get_opt('DEFAULT.speak_command'), words])
183 # Scan for a limited time and return AP names and bssid found.
184 # Access points we find will be put on the outgoing Queue, apQueue.
186 #Parameters:
188 # 'confFile' -- ConfigFile - Config file object
190 # 'apQueue' -- Queue - Queue on which to put AP profiles
192 # 'commandQueue' -- Queue - Queue from which to read commands
194 # 'logger' -- Logger - Python's logging facility
196 #Returns:
198 # nothing
199 def scanning_thread(confFile, apQueue, commandQueue, logger, exit_event):
200 logger.info("Begin thread.")
201 # Setup our essid pattern matcher
202 essid_pattern = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
203 bssid_pattern = re.compile( "Address\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
204 protocol_pattern = re.compile( "Protocol\s*(:|=)\s*IEEE 802.11\s*([abgn]+)", re.I | re.M | re.S )
205 mode_pattern = re.compile( "Mode\s*(:|=)\s*([^\n]+)", re.I | re.M | re.S )
206 channel_pattern = re.compile( "Channel\s*(:|=)*\s*(\d+)", re.I | re.M | re.S )
207 enckey_pattern = re.compile( "Encryption key\s*(:|=)\s*(on|off)", re.I | re.M | re.S )
208 signal_pattern = re.compile( "Signal level\s*(:|=)\s*(-?[0-9]+)", re.I | re.M | re.S )
210 access_points = {}
211 command = "scan"
212 while True:
213 try:
214 command = commandQueue.get_nowait()
215 logger.info("received command: %s" % (command, ))
216 command_read = True
217 except Queue.Empty:
218 command_read = False
219 if command == "scan":
220 logger.debug("Beginning scan pass")
221 # Some cards need to have the interface up to scan
222 if confFile.get_opt_as_bool('DEFAULT.ifup_required'):
223 # call ifconfig command and wait for return
224 shellcmd([confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface'), 'up'])
225 # update the signal strengths
226 scandata = Popen([confFile.get_opt('DEFAULT.iwlist_command'), confFile.get_opt('DEFAULT.interface'), 'scan'], stdout=PIPE).stdout.read()
227 # zero out the signal levels for all access points
228 for bssid in access_points:
229 access_points[bssid]['signal'] = 0
230 # split the scan data based on the address line
231 hits = scandata.split(' - ')
232 for hit in hits:
233 # set the defaults for profile template
234 profile = get_new_profile()
235 m = essid_pattern.search( hit )
236 if m:
237 # we found an essid
238 profile['essid'] = m.groups()[1]
239 m = bssid_pattern.search( hit ) # get BSSID from scan
240 if m: profile['bssid'] = m.groups()[1]
241 m = protocol_pattern.search( hit ) # get protocol from scan
242 if m: profile['protocol'] = m.groups()[1]
243 m = mode_pattern.search( hit ) # get mode from scan
244 if m: profile['mode'] = m.groups()[1]
245 m = channel_pattern.search( hit ) # get channel from scan
246 if m: profile['channel'] = m.groups()[1]
247 m = enckey_pattern.search( hit ) # get encryption key from scan
248 if m: profile['encrypted'] = ( m.groups()[1] == 'on' )
249 m = signal_pattern.search( hit ) # get signal strength from scan
250 if m: profile['signal'] = m.groups()[1]
251 access_points[ profile['bssid'] ] = profile
252 for bssid in access_points:
253 access_points[bssid]['available'] = ( access_points[bssid]['signal'] > 0 )
254 # Put all, now or previously, sensed access_points into apQueue
255 try:
256 logger.debug("Scanned profile: %s" % (access_points[ bssid ], ))
257 apQueue.put_nowait( access_points[bssid] )
258 except Queue.Full:
259 pass
260 if command_read:
261 commandQueue.task_done()
262 if exit_event.isSet():
263 logger.info("Exiting.")
264 return
265 if confFile.get_opt('DEFAULT.interface').find('ath') == 0:
266 sleep( 3 )
267 else:
268 sleep( 1 )
271 # Manage a connection; including reporting connection state,
272 # connecting/disconnecting from an AP, and returning current IP, ESSID, and BSSID.
273 class ConnectionManager():
274 # Create a new connection manager which can read a config file and send to scanning thread
275 # command Queue. A new manager checks for a pre-existing connection and takes
276 # its AP profile from the ESSID and BSSID to which it is currently attached.
278 #Parameters:
280 # 'confFile' -- ConfigFile - Config file object
282 # 'commandQueue' -- Queue - The Queue on which to put commands to the scanning thread
284 # 'logger' -- Logger - Python's logging facility
286 #Returns:
288 # ConnectionManager instance
289 def __init__( self, confFile, commandQueue, logger ):
290 self.confFile = confFile
291 self.commQueue = commandQueue
292 self.logger = logger
293 # is connection running?
294 self.state = False
295 if self.get_current_ip():
296 self.state = True
297 self.profile = self.confFile.get_profile( make_section_name(self.get_current_essid(), self.get_current_bssid()) )
299 # Change the interface state: up or down.
301 #Parameters:
303 # 'state' -- string - The state to which to change the interface.
305 #Returns:
307 # nothing
308 def if_change( self, state ):
309 if ( (state.lower() == 'up') or (state.lower() == 'down') ):
310 self.logger.info("changing interface state to %s" % (state, ))
311 # call ifconfig command and wait for return
312 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), state])
314 # Connect to the specified AP.
316 #Parameters:
318 # 'profile' -- dictionary - The profile for the AP (i.e. network) with which to connect.
320 # 'status' -- status implementer - Object which implements status interface.
322 #Returns:
324 # nothing
325 def connect_to_network( self, profile, status ):
326 self.profile = profile
327 if self.profile['bssid'] == '':
328 raise TypeError("Empty AP address")
329 msg = "Connecting to the %s (%s) network" % ( self.profile['essid'], self.profile['bssid'] )
330 say( msg )
331 self.logger.info(msg)
332 # ready to dance
333 # Let's run the connection prescript
334 if self.profile['con_prescript'].strip() != '':
335 # got something to execute
336 # run connection prescript through shell and wait for return
337 self.logger.info("executing connection prescript: %s" % (self.profile['con_prescript'], ))
338 shellcmd([self.profile['con_prescript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
339 status.show()
340 # Some cards need to have the interface up
341 if self.confFile.get_opt_as_bool('DEFAULT.ifup_required'):
342 self.if_change('up')
343 # Start building iwconfig command line, command
344 iwconfig_command = [ self.confFile.get_opt('DEFAULT.iwconfig_command') ]
345 iwconfig_command.append( self.confFile.get_opt('DEFAULT.interface') )
346 # Setting essid
347 iwconfig_command.append( 'essid' )
348 iwconfig_command.append( "'" + self.profile['essid'] + "'" )
349 # Setting nick
350 #iwconfig_commands.append( 'nick "%s"' % self.profile['essid'] )
351 # Setting key
352 iwconfig_command.append( 'key' )
353 if self.profile['key'] == '':
354 iwconfig_command.append( 'off' )
355 else:
356 iwconfig_command.append(self.profile['security'])
357 iwconfig_command.append( "'" + self.profile['key'] + "'" )
358 #iwconfig_commands.append( "key %s %s" % ( self.profile['security'], self.profile['key'] ) )
359 # Setting mode
360 if self.profile['mode'].lower() == 'master' or self.profile['mode'].lower() == 'auto':
361 self.profile['mode'] = 'Managed'
362 iwconfig_command.append( 'mode' )
363 iwconfig_command.append( self.profile['mode'] )
364 # Setting channel
365 if self.profile['channel'] != '':
366 iwconfig_command.append( 'channel' )
367 iwconfig_command.append( self.profile['channel'] )
368 # Now we do the ap by address (do this last since iwconfig seems to want it only there)
369 iwconfig_command.append( 'ap' )
370 iwconfig_command.append( self.profile['bssid'] )
371 # Some cards require a commit
372 if self.confFile.get_opt_as_bool('DEFAULT.commit_required'):
373 iwconfig_command.append( 'commit' )
374 self.logger.info("iwconfig_command: %s" % (iwconfig_command, ))
375 # call iwconfig command and wait for return
376 if not shellcmd(iwconfig_command): return
377 # Now normal network stuff
378 # Kill off any existing DHCP clients running
379 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
380 self.logger.info("Killing existing DHCP...")
381 try:
382 if self.confFile.get_opt('DHCP.kill_args') != '':
383 # call DHCP client kill command and wait for return
384 shellcmd([self.confFile.get_opt('DHCP.command'), self.confFile.get_opt('DHCP.kill_args')])
385 else:
386 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
387 except OSError:
388 print "failed to kill DHCP client"
389 sys.exit()
390 finally:
391 print "Stale pid file. Removing..."
392 os.remove(self.confFile.get_opt('DHCP.pidfile'))
393 # Kill off any existing WPA supplicants running
394 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
395 self.logger.info("Killing existing WPA supplicant...")
396 try:
397 if not self.confFile.get_opt('WPA.kill_command') != '':
398 # call WPA supplicant kill command and wait for return
399 shellcmd([self.confFile.get_opt('WPA.kill_command'), self.confFile.get_opt('WPA.kill_command')])
400 else:
401 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
402 except OSError:
403 print "failed to kill WPA supplicant"
404 sys.exit()
405 finally:
406 print "Stale pid file. Removing..."
407 os.remove(self.confFile.get_opt('WPA.pidfile'))
408 # Begin WPA supplicant
409 if self.profile['use_wpa'] :
410 self.logger.info("WPA args: %s" % (self.confFile.get_opt('WPA.args'), ))
411 status.update_message("WPA supplicant starting")
412 if sys.modules.has_key("gtk"):
413 while gtk.events_pending():
414 gtk.main_iteration(False)
415 # call WPA supplicant command and do not wait for return
416 wpa_proc = Popen([self.confFile.get_opt('WPA.command'), self.confFile.get_opt('WPA.args'), self.confFile.get_opt('DEFAULT.interface')])
417 if self.profile['use_dhcp'] :
418 self.logger.debug("Disable iwlist while dhcp in progress...")
419 try:
420 self.commQueue.put("pause")
421 except Queue.Full:
422 pass
423 status.update_message("Acquiring IP Address (DHCP)")
424 if sys.modules.has_key("gtk"):
425 while gtk.events_pending():
426 gtk.main_iteration(False)
427 # call DHCP client command and do not wait for return
428 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
429 dhcp_command.extend( self.confFile.get_opt('DHCP.args').split(' ') )
430 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
431 self.logger.info("dhcp_command: %s" % (dhcp_command, ))
432 dhcp_proc = Popen(dhcp_command, stdout=None)
433 timer = self.confFile.get_opt_as_int('DHCP.timeout') + 3
434 tick = 0.25
435 waiting = dhcp_proc.poll()
436 while waiting == None:
437 waiting = dhcp_proc.poll()
438 if timer < 0:
439 os.kill(dhcp_proc.pid, SIGTERM)
440 break
441 if sys.modules.has_key("gtk"):
442 while gtk.events_pending():
443 gtk.main_iteration(False)
444 timer -= tick
445 sleep(tick)
446 # Re-enable iwlist
447 try:
448 self.commQueue.put("scan")
449 except Queue.Full:
450 pass
451 if not self.get_current_ip():
452 status.update_message("Could not get IP address!")
453 if sys.modules.has_key("gtk"):
454 while gtk.events_pending():
455 gtk.main_iteration(False)
456 sleep(1)
457 if self.state:
458 self.disconnect_interface()
459 status.hide()
460 return
461 else:
462 status.update_message("Got IP address. Done.")
463 self.state = True
464 if sys.modules.has_key("gtk"):
465 while gtk.events_pending():
466 gtk.main_iteration(False)
467 sleep(2)
468 else:
469 ifconfig_command= "%s %s down; %s %s %s netmask %s" % ( self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), self.profile['ip'], self.profile['netmask'] )
470 route_command = "%s add default gw %s" % ( self.confFile.get_opt('DEFAULT.route_command'), self.profile['gateway'] )
471 resolv_contents = ''
472 if self.profile['domain'] != '': resolv_contents += "domain %s\n" % domain
473 if self.profile['dns1'] != '': resolv_contents += "nameserver %s\n" % dns1
474 if self.profile['dns2'] != '': resolv_contents += "nameserver %s\n" % dns2
475 if ( resolv_contents != '' ):
476 resolv_file=open('/etc/resolv.conf', 'w')
477 resolv_file.write(s)
478 resolv_file.close
479 if not shellcmd([ifconfig_command]): return
480 if not shellcmd([route_command]): return
481 self.state = True
482 # Let's run the connection postscript
483 con_postscript = self.profile['con_postscript']
484 if self.profile['con_postscript'].strip() != '':
485 self.logger.info("executing connection postscript: %s" % (self.profile['con_postscript'], ))
486 shellcmd([self.profile['con_postscript']],
487 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
488 "WIFIRADAR_ESSID": self.get_current_essid() or '',
489 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
490 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
493 status.hide()
495 # Disconnect from the AP with which a connection has been established/attempted.
497 #Parameters:
499 # nothing
501 #Returns:
503 # nothing
504 def disconnect_interface( self ):
505 msg = "Disconnecting"
506 say( msg )
507 self.logger.info(msg)
508 # Pause scanning while manipulating card
509 try:
510 self.commQueue.put("pause")
511 self.commQueue.join()
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 if not self.profile:
517 self.profile = self.confFile.get_profile(make_section_name(self.get_current_essid(), ''))
518 if not self.profile:
519 raise KeyError
520 # Let's run the disconnection prescript
521 if self.profile['dis_prescript'].strip() != '':
522 self.logger.info("executing disconnection prescript: %s" % (self.profile['dis_prescript'], ))
523 shellcmd([self.profile['dis_prescript']],
524 environment = { "WIFIRADAR_IP": self.get_current_ip() or '0.0.0.0',
525 "WIFIRADAR_ESSID": self.get_current_essid() or '',
526 "WIFIRADAR_BSSID": self.get_current_bssid() or '00:00:00:00:00:00',
527 "WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface') or ''
530 self.logger.info("Kill off any existing DHCP clients running...")
531 if os.path.isfile(self.confFile.get_opt('DHCP.pidfile')):
532 self.logger.info("Killing existing DHCP...")
533 try:
534 if self.confFile.get_opt('DHCP.kill_args').strip() != '':
535 dhcp_command = [ self.confFile.get_opt('DHCP.command') ]
536 dhcp_command.extend( self.confFile.get_opt('DHCP.kill_args').split(' ') )
537 dhcp_command.append( self.confFile.get_opt('DEFAULT.interface') )
538 self.logger.info("DHCP command: %s" % (dhcp_command, ))
539 # call DHCP client command and wait for return
540 if not shellcmd(dhcp_command): return
541 else:
542 self.logger.info("Killing DHCP manually...")
543 os.kill(int(open(self.confFile.get_opt('DHCP.pidfile'), mode='r').readline()), SIGTERM)
544 except OSError:
545 print "failed to kill DHCP client"
546 self.logger.info("Kill off any existing WPA supplicants running...")
547 if os.access(self.confFile.get_opt('WPA.pidfile'), os.R_OK):
548 self.logger.info("Killing existing WPA supplicant...")
549 try:
550 if not self.confFile.get_opt('WPA.kill_command') != '':
551 wpa_command = [ self.confFile.get_opt('WPA.kill_command').split(' ') ]
552 if not shellcmd(wpa_command): return
553 else:
554 os.kill(int(open(self.confFile.get_opt('WPA.pidfile'), mode='r').readline()), SIGTERM)
555 except OSError:
556 print "failed to kill WPA supplicant"
557 self.logger.info("Let's clear out the wireless stuff")
558 shellcmd([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface'), 'essid', 'any', 'key', 'off', 'mode', 'managed', 'channel', 'auto', 'ap', 'off'])
559 self.logger.info("Now take the interface down")
560 self.logger.info("Since it may be brought back up by the next scan, lets unset its IP")
561 shellcmd([self.confFile.get_opt('DEFAULT.ifconfig_command'), self.confFile.get_opt('DEFAULT.interface'), '0.0.0.0'])
562 # taking down the interface too quickly can crash my system, so pause a moment
563 sleep(1)
564 self.if_change('down')
565 # Let's run the disconnection postscript
566 if self.profile['dis_postscript'].strip() != '':
567 self.logger.info("executing disconnection postscript: %s" % (self.profile['dis_postscript'], ))
568 shellcmd([self.profile['dis_postscript']], environment={"WIFIRADAR_IF": self.confFile.get_opt('DEFAULT.interface')})
569 self.state = False
570 self.logger.info("Disconnect complete.")
571 # Begin scanning again
572 try:
573 self.commQueue.put("scan")
574 except Queue.Full:
575 pass
577 # Returns the current IP, if any, by calling ifconfig.
579 #Parameters:
581 # nothing
583 #Returns:
585 # string or None -- the IP address or None (if no there is no current connection)
586 def get_current_ip( self ):
587 ifconfig_command = [ confFile.get_opt('DEFAULT.ifconfig_command'), confFile.get_opt('DEFAULT.interface') ]
588 ifconfig_info = Popen(ifconfig_command, stdout=PIPE).stdout
589 # Be careful to the language (inet adr: in French for example)
591 # Hi Brian
593 # I'm using wifi-radar on a system with German translations (de_CH-UTF-8).
594 # There the string in ifconfig is inet Adresse for the IP which isn't
595 # found by the current get_current_ip function in wifi-radar. I changed
596 # the according line (#289; gentoo, v1.9.6-r1) to
597 # >ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
598 # which works on my system (LC_ALL=de_CH.UTF-8) and still works with LC_ALL=C.
600 # I'd be happy if you could incorporate this small change because as now
601 # I've got to change the file every time it is updated.
603 # Best wishes
605 # Simon
606 ip_re = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)')
607 line = ifconfig_info.read()
608 if ip_re.search( line ):
609 return ip_re.search( line ).group(1)
610 return None
612 # Returns the current ESSID, if any, by calling iwconfig.
614 #Parameters:
616 # nothing
618 #Returns:
620 # string or None -- the ESSID or None (if no there is no current association)
621 def get_current_essid( self ):
622 """Returns the current ESSID if any by calling iwconfig"""
623 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
624 # Be careful to the language (inet adr: in French for example)
625 essid_re = re.compile( "ESSID\s*(:|=)\s*\"([^\"]+)\"", re.I | re.M | re.S )
626 line = iwconfig_info.read()
627 if essid_re.search( line ):
628 return essid_re.search( line ).group(2)
629 return None
631 # Returns the current BSSID, if any, by calling iwconfig.
633 #Parameters:
635 # nothing
637 #Returns:
639 # string or None -- the BSSID or None (if no there is no current association)
640 def get_current_bssid( self ):
641 """Returns the current BSSID if any by calling iwconfig"""
642 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command'), self.confFile.get_opt('DEFAULT.interface')], stdout=PIPE).stdout
643 bssid_re = re.compile( "Access Point\s*(:|=)\s*([a-zA-Z0-9:]+)", re.I | re.M | re.S )
644 line = iwconfig_info.read()
645 if bssid_re.search( line ):
646 return bssid_re.search( line ).group(2)
647 return None
651 # The main user interface window for WiFi Radar. This class also is the control
652 # center for most of the rest of the operations.
653 class radar_window:
654 # Create a new radar_window.
656 #Parameters:
658 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
660 # 'apQueue' -- Queue - The Queue from which AP profiles are read.
662 # 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
664 # 'logger' -- Logger - Python's logging facility
666 #Returns:
668 # radar_window instance
669 def __init__(self, confFile, apQueue, commQueue, logger, exit_event):
670 global signal_xpm_none
671 global signal_xpm_low
672 global signal_xpm_barely
673 global signal_xpm_ok
674 global signal_xpm_best
675 global known_profile_icon
676 global unknown_profile_icon
677 global wifi_radar_icon
679 self.confFile = confFile
680 self.apQueue = apQueue
681 self.commandQueue = commQueue
682 self.logger = logger
683 self.access_points = {}
684 self.exit_event = exit_event
685 self.connection = None
687 self.known_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( known_profile_icon[0] ), known_profile_icon[0], False )
688 self.unknown_profile_icon = gtk.gdk.pixbuf_new_from_inline( len( unknown_profile_icon[0] ), unknown_profile_icon[0], False )
689 self.signal_none_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_none )
690 self.signal_low_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_low )
691 self.signal_barely_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_barely )
692 self.signal_ok_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_ok )
693 self.signal_best_pb = gtk.gdk.pixbuf_new_from_xpm_data( signal_xpm_best )
694 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL )
695 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
696 self.window.set_icon( icon )
697 self.window.set_border_width( 10 )
698 self.window.set_size_request( 550, 300 )
699 self.window.set_title( "WiFi Radar" )
700 self.window.connect( 'delete_event', self.delete_event )
701 self.window.connect( 'destroy', self.destroy )
702 # let's create all our widgets
703 self.current_network = gtk.Label()
704 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
705 self.current_network.show()
706 self.close_button = gtk.Button( "Close", gtk.STOCK_CLOSE )
707 self.close_button.show()
708 self.close_button.connect( 'clicked', self.delete_event, None )
709 self.about_button = gtk.Button( "About", gtk.STOCK_ABOUT )
710 self.about_button.show()
711 self.about_button.connect( 'clicked', self.show_about_info, None )
712 self.preferences_button = gtk.Button( "Preferences", gtk.STOCK_PREFERENCES )
713 self.preferences_button.show()
714 self.preferences_button.connect( 'clicked', self.edit_preferences, None )
715 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
716 self.pstore = gtk.ListStore( str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str )
717 self.plist = gtk.TreeView( self.pstore )
718 # The icons column, known and encryption
719 self.pix_cell = gtk.CellRendererPixbuf()
720 self.wep_cell = gtk.CellRendererPixbuf()
721 self.icons_cell = gtk.CellRendererText()
722 self.icons_col = gtk.TreeViewColumn()
723 self.icons_col.pack_start( self.pix_cell, False )
724 self.icons_col.pack_start( self.wep_cell, False )
725 self.icons_col.add_attribute( self.pix_cell, 'pixbuf', 1 )
726 self.icons_col.add_attribute( self.wep_cell, 'stock-id', 4 )
727 self.plist.append_column( self.icons_col )
728 # The AP column
729 self.ap_cell = gtk.CellRendererText()
730 self.ap_col = gtk.TreeViewColumn( "Access Point" )
731 self.ap_col.pack_start( self.ap_cell, True )
732 self.ap_col.add_attribute( self.ap_cell, 'text', 0 )
733 self.plist.append_column( self.ap_col )
734 # The signal column
735 self.sig_cell = gtk.CellRendererPixbuf()
736 self.signal_col = gtk.TreeViewColumn( "Signal" )
737 self.signal_col.pack_start( self.sig_cell, True )
738 self.signal_col.add_attribute( self.sig_cell, 'pixbuf', 5 )
739 self.plist.append_column( self.signal_col )
740 # The mode column
741 self.mode_cell = gtk.CellRendererText()
742 self.mode_col = gtk.TreeViewColumn( "Mode" )
743 self.mode_col.pack_start( self.mode_cell, True )
744 self.mode_col.add_attribute( self.mode_cell, 'text', 6 )
745 self.plist.append_column( self.mode_col )
746 # The protocol column
747 self.prot_cell = gtk.CellRendererText()
748 self.protocol_col = gtk.TreeViewColumn( "802.11" )
749 self.protocol_col.pack_start( self.prot_cell, True )
750 self.protocol_col.add_attribute( self.prot_cell, 'text', 7 )
751 self.plist.append_column( self.protocol_col )
752 # The channel column
753 self.channel_cell = gtk.CellRendererText()
754 self.channel_col = gtk.TreeViewColumn( "Channel" )
755 self.channel_col.pack_start( self.channel_cell, True )
756 self.channel_col.add_attribute( self.channel_cell, 'text', 8 )
757 self.plist.append_column( self.channel_col )
758 # DnD Ordering
759 self.plist.set_reorderable( True )
760 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
761 self.pstore.connect( 'row-deleted', self.update_auto_profile_order )
762 # enable/disable buttons based on the selected network
763 self.selected_network = self.plist.get_selection()
764 self.selected_network.connect( 'changed', self.on_network_selection, None )
765 # the list scroll bar
766 sb = gtk.VScrollbar( self.plist.get_vadjustment() )
767 sb.show()
768 self.plist.show()
769 # Add New button
770 self.new_button = gtk.Button( "_New" )
771 self.new_button.connect( 'clicked', self.create_new_profile, get_new_profile(), None )
772 self.new_button.show()
773 # Add Configure button
774 self.edit_button = gtk.Button( "C_onfigure" )
775 self.edit_button.connect( 'clicked', self.edit_profile, None )
776 self.edit_button.show()
777 self.edit_button.set_sensitive(False)
778 # Add Delete button
779 self.delete_button = gtk.Button( "_Delete" )
780 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
781 self.delete_button.show()
782 self.delete_button.set_sensitive(False)
783 # Add Connect button
784 self.connect_button = gtk.Button( "Co_nnect" )
785 self.connect_button.connect( 'clicked', self.connect_profile, None )
786 # Add Disconnect button
787 self.disconnect_button = gtk.Button( "D_isconnect" )
788 self.disconnect_button.connect( 'clicked', self.disconnect_profile, None )
789 # lets add our widgets
790 rows = gtk.VBox( False, 3 )
791 net_list = gtk.HBox( False, 0 )
792 listcols = gtk.HBox( False, 0 )
793 prows = gtk.VBox( False, 0 )
794 # lets start packing
795 # the network list
796 net_list.pack_start( self.plist, True, True, 0 )
797 net_list.pack_start( sb, False, False, 0 )
798 # the rows level
799 rows.pack_start( net_list , True, True, 0 )
800 rows.pack_start( self.current_network, False, True, 0 )
801 # the list columns
802 listcols.pack_start( rows, True, True, 0 )
803 listcols.pack_start( prows, False, False, 5 )
804 # the list buttons
805 prows.pack_start( self.new_button, False, False, 2 )
806 prows.pack_start( self.edit_button, False, False, 2 )
807 prows.pack_start( self.delete_button, False, False, 2 )
808 prows.pack_end( self.connect_button, False, False, 2 )
809 prows.pack_end( self.disconnect_button, False, False, 2 )
811 self.window.action_area.pack_start( self.about_button )
812 self.window.action_area.pack_start( self.preferences_button )
813 self.window.action_area.pack_start( self.close_button )
815 rows.show()
816 prows.show()
817 listcols.show()
818 self.window.vbox.add( listcols )
819 self.window.vbox.set_spacing( 3 )
820 self.window.show_all()
822 # Now, immediately hide these two. The proper one will be
823 # displayed later, based on interface state. -BEF-
824 self.disconnect_button.hide()
825 self.connect_button.hide()
826 self.connect_button.set_sensitive(False)
828 # set up connection manager for later use
829 self.connection = ConnectionManager( self.confFile, self.commandQueue, self.logger )
830 # set up status window for later use
831 self.status_window = StatusWindow( self )
832 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
834 # Add our known profiles in order
835 for profile_name in self.confFile.auto_profile_order:
836 profile_name = profile_name.strip()
837 self.access_points[profile_name] = self.confFile.get_profile(profile_name)
838 wep = None
839 if self.access_points[profile_name]['encrypted']:
840 wep = gtk.STOCK_DIALOG_AUTHENTICATION
841 if self.access_points[profile_name]['roaming']:
842 ap_name = self.access_points[profile_name]['essid'] + "\n" + ' Multiple APs'
843 else:
844 ap_name = self.access_points[profile_name]['essid'] + "\n" + self.access_points[profile_name]['bssid']
845 self.pstore.append([ap_name, self.known_profile_icon, self.access_points[profile_name]['known'], self.access_points[profile_name]['available'], wep, self.signal_none_pb, self.access_points[profile_name]['mode'], self.access_points[profile_name]['protocol'], self.access_points[profile_name]['channel'] ] )
846 # This is the first run (or, at least, no config file was present), so pop up the preferences window
847 if ( self.confFile.get_opt_as_bool('DEFAULT.new_file') == True ):
848 self.confFile.remove_option('DEFAULT', 'new_file')
849 self.edit_preferences(self.preferences_button)
851 # Begin running radar_window in Gtk event loop.
853 #Parameters:
855 # nothing
857 #Returns:
859 # nothing
860 def main( self ):
861 gtk.main()
863 # Quit application.
865 #Parameters:
867 # 'widget' -- gtk.Widget - The widget sending the event.
869 #Returns:
871 # nothing
872 def destroy( self, widget = None):
873 if self.status_window:
874 self.status_window.destroy()
875 gtk.main_quit()
877 # Kill scanning thread, update profile order for config file, and ask to be destroyed.
879 #Parameters:
881 # 'widget' -- gtk.Widget - The widget sending the event.
883 # 'data' -- tuple - list of arbitrary arguments (not used)
885 #Returns:
887 # boolean -- always return False (i.e. do not propigate the signal which called)
888 def delete_event( self, widget, data = None ):
889 # Let other threads know it is time to exit
890 self.exit_event.set()
891 # Wait for all other threads to exit before continuing
892 while threading.activeCount() > 1:
893 sleep(0.25)
894 self.destroy()
895 return False
897 # Update the current ip and essid shown to user.
899 #Parameters:
901 # nothing
903 #Returns:
905 # nothing
906 def update_network_info(self):
907 if self.connection and self.connection.state:
908 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()))
909 else:
910 self.current_network.set_text("Not Connected.")
912 # Set the state of connect/disconnect buttons based on whether we have a connection.
914 #Parameters:
916 # nothing
918 #Returns:
920 # nothing
921 def update_connect_buttons(self):
922 if self.connection and self.connection.state:
923 self.connect_button.hide()
924 self.disconnect_button.show()
925 else:
926 self.disconnect_button.hide()
927 self.connect_button.show()
929 # Updates the on-screen profiles list.
931 #Parameters:
933 # nothing
935 #Returns:
937 # boolean -- always return True
938 def update_plist_items( self ):
939 # Indicate to PyGtk that only one Gtk thread should run here
940 gtk.gdk.threads_enter()
941 while True:
942 # Get APs scanned by iwlist
943 try:
944 ap = self.apQueue.get_nowait()
945 except Queue.Empty:
946 break
947 else:
948 profile = self.confFile.get_profile(make_section_name(ap['essid'], ap['bssid']))
949 if not profile:
950 profile = self.confFile.get_profile(make_section_name(ap['essid'], ''))
951 if not profile:
952 profile = get_new_profile()
953 if profile['roaming']:
954 prow_iter = self.get_row_by_ap(ap['essid'], ' Multiple APs')
955 else:
956 prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
957 wep = None
958 if prow_iter != None:
959 # the AP is in the list of APs on the screen
960 if profile['roaming']:
961 apname = make_section_name(ap['essid'], '')
962 else:
963 apname = make_section_name(ap['essid'], ap['bssid'])
964 if self.access_points.has_key(apname):
965 # This AP has been configured and is/should be stored in the config file
966 ap['known'] = self.access_points[apname]['known']
967 self.access_points[apname]['available'] = ap['available']
968 self.access_points[apname]['encrypted'] = ap['encrypted']
969 self.access_points[apname]['signal'] = ap['signal']
970 self.access_points[apname]['mode'] = ap['mode']
971 self.access_points[apname]['protocol'] = ap['protocol']
972 #if not (self.access_points[apname]['roaming'] and (self.access_points[apname]['signal'] > ap['signal'])) or (self.access_points[apname]['bssid'] == ''):
973 self.access_points[apname]['channel'] = ap['channel']
974 self.access_points[apname]['bssid'] = ap['bssid']
975 # Set the 'known' values; False is default, overridden to True by self.access_points
976 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
977 self.pstore.set_value(prow_iter, 2, ap['known'])
978 self.pstore.set_value(prow_iter, 3, ap['available'])
979 if ap['encrypted']:
980 wep = gtk.STOCK_DIALOG_AUTHENTICATION
981 self.pstore.set_value(prow_iter, 4, wep)
982 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(ap['signal']))
983 self.pstore.set_value(prow_iter, 6, ap['mode'])
984 self.pstore.set_value(prow_iter, 7, ap['protocol'])
985 self.pstore.set_value(prow_iter, 8, ap['channel'])
986 #print "update_plist_items: profile update", profile[ 'essid' ], profile['bssid']
987 #for val in self.pstore[prow_iter]:
988 #print val,
989 else:
990 # the AP is not in the list of APs on the screen
991 if profile['roaming']:
992 ap_name = ap['essid'] + "\n" + ' Multiple APs'
993 else:
994 ap_name = ap['essid'] + "\n" + ap['bssid']
995 self.pstore.append([ap_name, self.pixbuf_from_known(ap['known']), ap['known'], ap['available'], wep, self.pixbuf_from_signal(ap['signal']), ap['mode'], ap['protocol'], ap['channel']])
996 #print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']
997 # Allow other Gtk threads to run
998 gtk.gdk.threads_leave()
999 #print "update_plist_items: Empty apQueue"
1000 return True
1002 # Return the proper icon for a value of known.
1004 #Parameters:
1006 # 'known' -- boolean - Whether the AP is known (i.e. configured)
1008 #Returns:
1010 # gtk.gdk.Pixbuf -- icon for a known or unknown AP
1011 def pixbuf_from_known( self, known ):
1012 """ return the proper icon for value of known """
1013 if known:
1014 return self.known_profile_icon
1015 else:
1016 return self.unknown_profile_icon
1018 # Return an icon indicating the signal level.
1020 #Parameters:
1022 # 'signal' -- integer - signal level reported by iwlist (may be arbitrary scale in 0-100 or -X dBm)
1024 #Returns:
1026 # gtk.gdk.Pixbuf -- 1 of 5 icons indicating signal level
1027 def pixbuf_from_signal( self, signal ):
1028 signal = int( signal )
1029 # shift signal up by 80 to convert dBm scale to arbitrary scale
1030 if signal < 0: signal = signal + 80
1031 #print "signal level:", signal
1032 if signal < 3:
1033 return self.signal_none_pb
1034 elif signal < 12:
1035 return self.signal_low_pb
1036 elif signal < 20:
1037 return self.signal_barely_pb
1038 elif signal < 35:
1039 return self.signal_ok_pb
1040 elif signal >= 35:
1041 return self.signal_best_pb
1042 else:
1043 return None
1045 # Return row which holds specified ESSID and BSSID.
1047 #Parameters:
1049 # 'essid' -- string - ESSID to match
1051 # 'bssid' -- string - BSSID to match
1053 #Returns:
1055 # gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
1056 def get_row_by_ap( self, essid, bssid ):
1057 if bssid == "roaming":
1058 for row in self.pstore:
1059 if (row[0][:row[0].find("\n")] == essid):
1060 #print "roaming match:", row.iter, essid, bssid
1061 return row.iter
1062 else:
1063 for row in self.pstore:
1064 if (row[0] == essid + "\n" + bssid):
1065 #print "normal match:", row.iter, essid, bssid
1066 return row.iter
1067 return None
1069 # Enable/disable buttons based on the selected network.
1071 #Parameters:
1073 # 'widget' -- gtk.Widget - The widget sending the event.
1075 # 'data' -- tuple - list of arbitrary arguments (not used)
1077 #Returns:
1079 # nothing
1080 def on_network_selection( self, widget, data = None ):
1081 ( store, selected_iter ) = self.selected_network.get_selected()
1082 #print self.access_points[( make_section_name( store.get_value( selected_iter, 0 ), store.get_value( selected_iter, 1 ) ) )]
1083 # if no networks are selected, disable all buttons except New
1084 # (this occurs after a drag-and-drop)
1085 if selected_iter == None:
1086 self.edit_button.set_sensitive(False)
1087 self.delete_button.set_sensitive(False)
1088 self.connect_button.set_sensitive(False)
1089 return
1090 # enable/disable buttons
1091 self.connect_button.set_sensitive(True)
1092 if (store.get_value(selected_iter, 2) == True): # is selected network known?
1093 self.edit_button.set_sensitive(True)
1094 self.delete_button.set_sensitive(True)
1095 else:
1096 self.edit_button.set_sensitive(True)
1097 self.delete_button.set_sensitive(False)
1099 # Init and run the about dialog
1101 #Parameters:
1103 # 'widget' -- gtk.Widget - The widget sending the event.
1105 # 'data' -- tuple - list of arbitrary arguments (not used)
1107 #Returns:
1109 # nothing
1110 def show_about_info( self, widget, data=None ):
1111 about = about_dialog()
1112 about.run()
1113 about.destroy()
1115 # Init and run the preferences dialog
1117 #Parameters:
1119 # 'widget' -- gtk.Widget - The widget sending the event.
1121 # 'data' -- tuple - list of arbitrary arguments (not used)
1123 #Returns:
1125 # nothing
1126 def edit_preferences( self, widget, data=None ):
1127 # get raw strings from config file
1128 self.confFile.raw = True
1129 prefs = preferences_dialog( self, self.confFile )
1130 response = prefs.run()
1131 prefs.destroy()
1132 if response == int(gtk.RESPONSE_ACCEPT):
1133 prefs.save()
1134 # get cooked strings from config file
1135 self.confFile.raw = False
1137 # Respond to a request to create a new AP profile
1139 #Parameters:
1141 # 'widget' -- gtk.Widget - The widget sending the event.
1143 # 'profile' -- dictionary - The AP profile to use as basis for new profile.
1145 # 'data' -- tuple - list of arbitrary arguments (not used)
1147 #Returns:
1149 # boolean -- True if a profile was created and False if profile creation was canceled.
1150 def create_new_profile( self, widget, profile, data=None ):
1151 profile_editor = profile_dialog( self, profile )
1152 try:
1153 profile = profile_editor.run()
1154 except ValueError:
1155 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1156 del error_dlg
1157 return False
1158 finally:
1159 profile_editor.destroy()
1160 if profile:
1161 (store, selected_iter) = self.plist.get_selection().get_selected()
1162 store.remove(selected_iter)
1163 if profile['roaming']:
1164 apname = make_section_name(profile['essid'], '')
1165 else:
1166 apname = make_section_name(profile['essid'], profile['bssid'])
1167 # Check that the ap does not exist already
1168 if apname in self.confFile.profiles():
1169 error_dlg = ErrorDialog(None, "A profile for %s already exists" % (apname))
1170 del error_dlg
1171 # try again
1172 self.access_points[ apname ] = profile
1173 self.confFile.set_section( apname, profile )
1174 # if it is not in the auto_profile_order add it
1175 if apname not in self.confFile.auto_profile_order:
1176 self.confFile.auto_profile_order.insert(0, apname)
1177 # add to the store
1178 wep = None
1179 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
1180 try:
1181 self.confFile.write()
1182 except IOError, (error_number, error_str):
1183 if error_number == errno.ENOENT:
1184 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1185 del error_dlg
1186 else:
1187 raise IOError(error_number, error_str)
1188 # Add AP to the list displayed to user
1189 if profile['roaming']:
1190 ap_name = profile['essid'] + "\n" + ' Multiple APs'
1191 while True:
1192 prow_iter = self.get_row_by_ap(profile['essid'], 'roaming')
1193 if prow_iter:
1194 self.pstore.remove(prow_iter)
1195 else:
1196 break
1197 else:
1198 ap_name = profile['essid'] + "\n" + profile['bssid']
1199 self.pstore.prepend([ap_name, self.pixbuf_from_known(profile['known']), profile['known'], profile['available'], wep, self.pixbuf_from_signal(profile['signal']), profile['mode'], profile['protocol'], profile['channel']])
1200 return True
1201 else:
1202 # Did not create new profile
1203 return False
1205 # Respond to a request to edit an AP profile. Edit selected AP profile if it
1206 # is known. Otherwise, create a new profile with the selected ESSID and BSSID.
1208 #Parameters:
1210 # 'widget' -- gtk.Widget - The widget sending the event.
1212 # 'data' -- tuple - list of arbitrary arguments (not used)
1214 #Returns:
1216 # nothing
1217 def edit_profile(self, widget, data=None):
1218 (store, selected_iter) = self.plist.get_selection().get_selected()
1219 if not selected_iter: return
1220 (essid, bssid) = str(self.pstore.get_value(selected_iter, 0)).split("\n")
1221 if bssid == ' Multiple APs':
1222 apname = make_section_name(essid, '')
1223 else:
1224 apname = make_section_name(essid, bssid)
1225 profile = self.confFile.get_profile(apname)
1226 if profile:
1227 profile['bssid'] = self.access_points[apname]['bssid']
1228 profile_editor = profile_dialog(self, profile)
1229 try:
1230 edited_profile = profile_editor.run()
1231 except ValueError:
1232 error_dlg = ErrorDialog(None, "Cannot save empty ESSID")
1233 del error_dlg
1234 return False
1235 finally:
1236 profile_editor.destroy()
1237 if edited_profile:
1238 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
1239 try:
1240 self.commandQueue.put('pause')
1241 self.commandQueue.join()
1242 except Queue.Full:
1243 pass
1244 if profile['roaming']:
1245 old_ap = make_section_name(profile['essid'], '')
1246 else:
1247 old_ap = make_section_name(profile['essid'], profile['bssid'])
1248 self.delete_profile(selected_iter, old_ap)
1249 self.confFile.remove_section(old_ap)
1250 # Add AP to the list displayed to user
1251 try:
1252 self.apQueue.put_nowait(edited_profile)
1253 self.commandQueue.put('scan')
1254 except Queue.Full:
1255 pass
1256 if edited_profile['roaming']:
1257 apname = make_section_name(edited_profile['essid'], '')
1258 while True:
1259 prow_iter = self.get_row_by_ap(edited_profile['essid'], 'roaming')
1260 if prow_iter:
1261 self.pstore.remove(prow_iter)
1262 else:
1263 break
1264 else:
1265 apname = make_section_name(edited_profile['essid'], edited_profile['bssid'])
1266 self.access_points[apname] = edited_profile
1267 self.confFile.set_section(apname, edited_profile)
1268 try:
1269 self.confFile.write()
1270 except IOError, (error_number, error_str):
1271 if error_number == errno.ENOENT:
1272 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1273 del error_dlg
1274 else:
1275 raise IOError(error_number, error_str)
1276 else:
1277 # The AP does not already have a profile
1278 profile = get_new_profile()
1279 ( profile['essid'], profile['bssid'] ) = store.get_value( selected_iter, 0 ).split("\n")
1280 self.create_new_profile( widget, profile, data )
1282 # Delete an AP profile (i.e. make profile unknown)
1284 #Parameters:
1286 # 'selected_iter' -- gtk.TreeIter - The selected row.
1288 # 'apname' -- string - The configuration file section to remove
1290 #Returns:
1292 # nothing
1293 def delete_profile(self, selected_iter, apname):
1294 # Remove it
1295 del self.access_points[apname]
1296 self.confFile.remove_section(apname)
1297 self.logger.info(apname)
1298 if apname in self.confFile.auto_profile_order:
1299 self.confFile.auto_profile_order.remove(apname)
1300 self.pstore.remove(selected_iter)
1301 # Let's save our current state
1302 self.update_auto_profile_order()
1303 try:
1304 self.confFile.write()
1305 except IOError, (error_number, error_str):
1306 if error_number == errno.ENOENT:
1307 error_dlg = ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str))
1308 del error_dlg
1309 else:
1310 raise IOError(error_number, error_str)
1312 # Respond to a request to delete an AP profile (i.e. make profile unknown)
1313 # Check with user first.
1315 #Parameters:
1317 # 'widget' -- gtk.Widget - The widget sending the event.
1319 # 'data' -- tuple - list of arbitrary arguments (not used)
1321 #Returns:
1323 # nothing
1324 def delete_profile_with_check(self, widget, data=None):
1325 (store, selected_iter) = self.plist.get_selection().get_selected()
1326 if not selected_iter: return
1327 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
1328 if bssid == ' Multiple APs':
1329 apname = make_section_name(essid, '')
1330 else:
1331 apname = make_section_name(essid, bssid)
1332 profile = self.confFile.get_profile(apname)
1333 if profile['roaming']:
1334 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s profile?" % (essid, ))
1335 else:
1336 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the %s (%s) profile?" % (essid, bssid))
1337 known = store.get_value( selected_iter, 1 )
1338 if not known: return
1339 res = dlg.run()
1340 dlg.destroy()
1341 del dlg
1342 if res == gtk.RESPONSE_NO:
1343 return
1344 self.delete_profile(selected_iter, apname)
1346 # Respond to a request to connect to an AP.
1348 #Parameters:
1350 # 'widget' -- gtk.Widget - The widget sending the event.
1352 # 'profile' -- dictionary - The AP profile to which to connect.
1354 # 'data' -- tuple - list of arbitrary arguments (not used)
1356 #Returns:
1358 # nothing
1359 def connect_profile( self, widget, profile, data=None ):
1360 ( store, selected_iter ) = self.plist.get_selection().get_selected()
1361 if not selected_iter: return
1362 ( essid, bssid ) = store.get_value( selected_iter, 0 ).split("\n")
1363 known = store.get_value( selected_iter, 2 )
1364 if not known:
1365 dlg = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "This network does not have a profile configured.\n\nWould you like to create one now?")
1366 res = dlg.run()
1367 dlg.destroy()
1368 del dlg
1369 if res == gtk.RESPONSE_NO:
1370 return
1371 profile = get_new_profile()
1372 profile['essid'] = essid
1373 profile['bssid'] = bssid
1374 if not self.create_new_profile( widget, profile, data ):
1375 return
1376 else:
1377 # Attempt to match to known profiles, normal then roaming
1378 profile = self.confFile.get_profile(make_section_name(essid, bssid))
1379 if not profile:
1380 apname = make_section_name(essid, '')
1381 profile = self.confFile.get_profile(apname)
1382 profile['bssid'] = self.access_points[apname]['bssid']
1383 profile['channel'] = 'auto'
1384 profile['mode'] = 'managed'
1385 self.connection.connect_to_network(profile, self.status_window)
1387 # Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
1389 #Parameters:
1391 # 'widget' -- gtk.Widget - The widget sending the event.
1393 # 'data' -- tuple - list of arbitrary arguments (not used)
1395 #Returns:
1397 # nothing
1398 def disconnect_profile( self, widget, data=None ):
1399 if data == "cancel":
1400 self.status_window.update_message("Canceling connection...")
1401 if sys.modules.has_key("gtk"):
1402 while gtk.events_pending():
1403 gtk.main_iteration(False)
1404 sleep(1)
1405 self.connection.disconnect_interface()
1407 # Update the config file auto profile order from the on-screen order
1409 #Parameters:
1411 # 'widget' -- gtk.Widget - The widget sending the event.
1413 # 'data' -- tuple - list of arbitrary arguments (not used)
1415 # 'data2' -- tuple - list of arbitrary arguments (not used)
1417 #Returns:
1419 # nothing
1420 def update_auto_profile_order( self, widget = None, data = None, data2 = None ):
1421 # recreate the auto_profile_order
1422 auto_profile_order = []
1423 piter = self.pstore.get_iter_first()
1424 while piter:
1425 # only if it's known
1426 if self.pstore.get_value(piter, 2) == True:
1427 (essid, bssid) = self.pstore.get_value(piter, 0).split("\n")
1428 if bssid == ' Multiple APs':
1429 apname = make_section_name(essid, '')
1430 else:
1431 apname = make_section_name(essid, bssid)
1432 auto_profile_order.append(apname)
1433 piter = self.pstore.iter_next(piter)
1434 self.confFile.auto_profile_order = auto_profile_order
1435 try:
1436 self.confFile.write()
1437 except IOError, (error_number, error_str):
1438 if error_number == errno.ENOENT:
1439 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1440 del error_dlg
1441 else:
1442 raise IOError(error_number, error_str)
1445 # Button to allow user to choose a file and put value into specified gtk.Entry
1446 class file_browse_button(gtk.Button):
1447 # Create a button to simulate a File/Open
1449 #Parameters:
1451 # 'parent' -- gtk.Object -- Usually, the calling window.
1453 # 'entry' -- gtk.Entry -- The text entry to update with user selection.
1455 #Returns:
1457 # file_browse_button instance
1458 def __init__( self, parent, entry ):
1459 self.parent_window = parent
1460 self.entry = entry
1461 gtk.Button.__init__(self, "Browse", None)
1462 #self.
1463 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)
1464 self.connect("clicked", self.browse_files)
1466 # Show filechooser dialog and get user selection
1468 #Parameters:
1470 # 'widget' -- gtk.Widget -- The widget sending the event.
1472 #Returns:
1474 # nothing
1476 #NOTES:
1478 # updates entry value
1480 def browse_files( self, widget ):
1481 self.browser_dialog.set_filename(self.entry.get_text())
1482 self.browser_dialog.run()
1483 self.entry.set_text(self.browser_dialog.get_filename())
1484 self.browser_dialog.destroy()
1487 # Simple dialog to report an error to the user.
1488 class ErrorDialog:
1489 # Create a new ErrorDialog.
1491 #Parameters:
1493 # 'parent' -- gtk.Object - Usually, the calling window.
1495 # 'message' -- string - The message to display to the user.
1497 #Returns:
1499 # ErrorDialog instance
1500 def __init__( self, parent, message ):
1501 dialog = gtk.MessageDialog( parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message )
1502 dialog.run()
1503 dialog.destroy()
1504 del dialog
1507 # The preferences dialog. Edits non-profile sections of the config file.
1508 class preferences_dialog:
1509 # Create a new preferences_dialog.
1511 #Parameters:
1513 # 'parent' -- gtk.Object - Usually, the calling window.
1515 # 'confFile' -- ConfigFile - The config file in which to store/read settings.
1517 #Returns:
1519 # preferences_dialog instance
1520 def __init__( self, parent, confFile ):
1521 global wifi_radar_icon
1522 self.parent = parent
1523 self.confFile = confFile
1524 self.dialog = gtk.Dialog('WiFi Radar Preferences', self.parent.window,
1525 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1526 ( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT ) )
1527 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1528 self.dialog.set_icon( icon )
1529 self.dialog.set_resizable( True )
1530 self.dialog.set_transient_for( self.parent.window )
1531 self.tooltips = gtk.Tooltips()
1533 # set up preferences widgets
1535 # build everything in a tabbed notebook
1536 self.prefs_notebook = gtk.Notebook()
1538 ### General tab
1539 self.general_page = gtk.VBox()
1540 # auto detect wireless device
1541 self.w_auto_detect = gtk.CheckButton("Auto-detect wireless device")
1543 self.w_auto_detect.set_active( self.confFile.get_opt('DEFAULT.interface') == "auto_detect" )
1544 self.w_auto_detect.connect("toggled", self.toggle_auto_detect)
1545 self.tooltips.set_tip(self.w_auto_detect, "Automatically select wireless device to configure")
1546 self.general_page.pack_start(self.w_auto_detect, False, False, 5)
1548 # network interface selecter
1549 self.w_interface = gtk.combo_box_entry_new_text()
1550 iwconfig_info = Popen([self.confFile.get_opt('DEFAULT.iwconfig_command')], stdout=PIPE).stdout
1551 wireless_devices = [ (x[0:x.find(" ")]) for x in iwconfig_info if ("ESSID" in x)]
1552 for device in wireless_devices:
1553 if device != self.confFile.get_opt('DEFAULT.interface'):
1554 self.w_interface.append_text(device)
1555 if self.confFile.get_opt('DEFAULT.interface') != "auto_detect":
1556 self.w_interface.prepend_text( self.confFile.get_opt('DEFAULT.interface') )
1557 self.w_interface.set_active(0)
1558 self.w_interface_label = gtk.Label("Wireless device")
1559 self.w_hbox1 = gtk.HBox(False, 0)
1560 self.w_hbox1.pack_start(self.w_interface_label, False, False, 5)
1561 self.w_hbox1.pack_start(self.w_interface, True, True, 0)
1562 self.w_interface.set_sensitive( self.confFile.get_opt('DEFAULT.interface') != "auto_detect" )
1563 self.general_page.pack_start(self.w_hbox1, False, False, 5)
1565 # scan timeout (spin button of integers from 1 to 100)
1566 #self.time_in_seconds = gtk.Adjustment( self.confFile.get_opt_as_int('DEFAULT.scan_timeout'),1,100,1,1,0 )
1567 #self.w_scan_timeout = gtk.SpinButton(self.time_in_seconds, 1, 0)
1568 #self.w_scan_timeout.set_update_policy(gtk.UPDATE_IF_VALID)
1569 #self.w_scan_timeout.set_numeric(True)
1570 #self.w_scan_timeout.set_snap_to_ticks(True)
1571 #self.w_scan_timeout.set_wrap(False)
1572 #self.tooltips.set_tip(self.w_scan_timeout, "How long should WiFi Radar scan for access points?")
1573 #self.w_scan_timeout_label = gtk.Label("Scan timeout (seconds)")
1574 #self.w_hbox2 = gtk.HBox(False, 0)
1575 #self.w_hbox2.pack_start(self.w_scan_timeout_label, False, False, 5)
1576 #self.w_hbox2.pack_start(self.w_scan_timeout, True, True, 0)
1577 #self.general_page.pack_start(self.w_hbox2, False, False, 5)
1579 # speak up
1580 self.w_speak_up = gtk.CheckButton("Use speak-up")
1581 self.w_speak_up.set_active( self.confFile.get_opt_as_bool('DEFAULT.speak_up') )
1582 self.w_speak_up.connect("toggled", self.toggle_speak)
1583 self.tooltips.set_tip(self.w_speak_up, "Should I speak up when connecting to a network? (If you have a speech command)")
1584 self.general_page.pack_start(self.w_speak_up, False, False, 5)
1586 # speak up command
1587 self.w_speak_cmd = gtk.Entry()
1588 self.w_speak_cmd.set_width_chars(16)
1589 self.tooltips.set_tip(self.w_speak_cmd, "The command to use to speak feedback")
1590 self.w_speak_cmd.set_text(self.confFile.get_opt('DEFAULT.speak_command'))
1591 self.w_speak_cmd_label = gtk.Label("Speak Command")
1592 self.w_speak_cmd_button = file_browse_button(self.dialog, self.w_speak_cmd)
1593 self.w_speak_cmd_button.set_sensitive(self.w_speak_up.get_active())
1594 self.w_hbox3 = gtk.HBox(False, 0)
1595 self.w_hbox3.pack_start(self.w_speak_cmd_label, True, False, 5)
1596 self.w_hbox3.pack_start(self.w_speak_cmd, False, False, 0)
1597 self.w_hbox3.pack_start(self.w_speak_cmd_button, False, False, 0)
1598 self.w_speak_cmd.set_sensitive(self.w_speak_up.get_active())
1599 self.general_page.pack_start(self.w_hbox3, False, False, 5)
1601 # commit required
1602 self.w_commit_required = gtk.CheckButton("Commit required")
1603 self.w_commit_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.commit_required') )
1604 self.tooltips.set_tip(self.w_commit_required, "Check this box if your card requires a \"commit\" command with iwconfig")
1605 self.general_page.pack_start(self.w_commit_required, False, False, 5)
1607 # ifup required
1608 self.w_ifup_required = gtk.CheckButton("Ifup required")
1609 self.w_ifup_required.set_active( self.confFile.get_opt_as_bool('DEFAULT.ifup_required') )
1610 self.tooltips.set_tip(self.w_ifup_required, "Check this box if your system requires the interface to be brought up first")
1611 self.general_page.pack_start(self.w_ifup_required, False, False, 5)
1613 self.prefs_notebook.append_page(self.general_page, gtk.Label("General"))
1614 ### End of General tab
1616 ### Advanced tab
1617 # table to use for layout of following command configurations
1618 self.cmds_table = gtk.Table()
1620 # ifconfig command
1621 self.ifconfig_cmd = gtk.Entry()
1622 self.ifconfig_cmd.set_width_chars(32)
1623 self.tooltips.set_tip(self.ifconfig_cmd, "The command to use to configure the network card")
1624 self.ifconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.ifconfig_command'))
1625 self.ifconfig_cmd_label = gtk.Label("Network interface configure command")
1626 self.ifconfig_cmd_button = file_browse_button(self.dialog, self.ifconfig_cmd)
1627 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1628 self.cmds_table.attach(self.ifconfig_cmd_label, 1, 2, 1, 2, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1629 self.cmds_table.attach(self.ifconfig_cmd, 2, 3, 1, 2, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1630 self.cmds_table.attach(self.ifconfig_cmd_button, 3, 4, 1, 2, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1632 # iwconfig command
1633 self.iwconfig_cmd = gtk.Entry()
1634 self.iwconfig_cmd.set_width_chars(32)
1635 self.tooltips.set_tip(self.iwconfig_cmd, "The command to use to configure the wireless connection")
1636 self.iwconfig_cmd.set_text(self.confFile.get_opt('DEFAULT.iwconfig_command'))
1637 self.iwconfig_cmd_label = gtk.Label("Wireless connection configure command")
1638 self.iwconfig_cmd_button = file_browse_button(self.dialog, self.iwconfig_cmd)
1639 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1640 self.cmds_table.attach(self.iwconfig_cmd_label, 1, 2, 2, 3, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1641 self.cmds_table.attach(self.iwconfig_cmd, 2, 3, 2, 3, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1642 self.cmds_table.attach(self.iwconfig_cmd_button, 3, 4, 2, 3, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1644 # iwlist command
1645 self.iwlist_cmd = gtk.Entry()
1646 self.iwlist_cmd.set_width_chars(32)
1647 self.tooltips.set_tip(self.iwlist_cmd, "The command to use to scan for access points")
1648 self.iwlist_cmd.set_text(self.confFile.get_opt('DEFAULT.iwlist_command'))
1649 self.iwlist_cmd_label = gtk.Label("Wireless scanning command")
1650 self.iwlist_cmd_button = file_browse_button(self.dialog, self.iwlist_cmd)
1651 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1652 self.cmds_table.attach(self.iwlist_cmd_label, 1, 2, 3, 4, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1653 self.cmds_table.attach(self.iwlist_cmd, 2, 3, 3, 4, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1654 self.cmds_table.attach(self.iwlist_cmd_button, 3, 4, 3, 4, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1656 # route command
1657 self.route_cmd = gtk.Entry()
1658 self.route_cmd.set_width_chars(32)
1659 self.tooltips.set_tip(self.route_cmd, "The command to use to configure the network routing")
1660 self.route_cmd.set_text(self.confFile.get_opt('DEFAULT.route_command'))
1661 self.route_cmd_label = gtk.Label("Network route configure command")
1662 self.route_cmd_button = file_browse_button(self.dialog, self.route_cmd)
1663 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1664 self.cmds_table.attach(self.route_cmd_label, 1, 2, 4, 5, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1665 self.cmds_table.attach(self.route_cmd, 2, 3, 4, 5, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1666 self.cmds_table.attach(self.route_cmd_button, 3, 4, 4, 5, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1668 # log file
1669 self.logfile_entry = gtk.Entry()
1670 self.logfile_entry.set_width_chars(32)
1671 self.tooltips.set_tip(self.logfile_entry, "The file in which to save logging info")
1672 self.logfile_entry.set_text(self.confFile.get_opt('DEFAULT.logfile'))
1673 self.logfile_label = gtk.Label("Log file")
1674 self.logfile_button = file_browse_button(self.dialog, self.logfile_entry)
1675 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1676 self.cmds_table.attach(self.logfile_label, 1, 2, 5, 6, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1677 self.cmds_table.attach(self.logfile_entry, 2, 3, 5, 6, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1678 self.cmds_table.attach(self.logfile_button, 3, 4, 5, 6, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1680 # log level (spin button of integers from 0 to 50 by 5's)
1681 self.loglevel = gtk.SpinButton(gtk.Adjustment(self.confFile.get_opt_as_int('DEFAULT.loglevel'), 0, 50, 5, 5, 0), 1, 0)
1682 self.loglevel.set_update_policy(gtk.UPDATE_IF_VALID)
1683 self.loglevel.set_numeric(True)
1684 self.loglevel.set_snap_to_ticks(True)
1685 self.loglevel.set_wrap(False)
1686 self.tooltips.set_tip(self.loglevel, "How much detail to save in log file. Larger numbers provide less detail and smaller numbers, more detail.")
1687 self.loglevel.set_text(self.confFile.get_opt('DEFAULT.loglevel'))
1688 self.loglevel_label = gtk.Label("Log level")
1689 # (widget, l, r, t, b, xopt, yopt, xpad, ypad)
1690 self.cmds_table.attach(self.loglevel_label, 1, 2, 6, 7, gtk.FILL|gtk.EXPAND, 0, 5, 0)
1691 self.cmds_table.attach(self.loglevel, 2, 3, 6, 7, gtk.FILL|gtk.EXPAND, 0, 0, 0)
1693 self.prefs_notebook.append_page(self.cmds_table, gtk.Label("Advanced"))
1694 ### End of Advanced tab
1696 ### DHCP tab
1697 # table to use for layout of DHCP prefs
1698 self.dhcp_table = gtk.Table()
1700 self.dhcp_cmd = gtk.Entry()
1701 self.dhcp_cmd.set_width_chars(32)
1702 self.tooltips.set_tip(self.dhcp_cmd, "The command to use for automatic network configuration")
1703 self.dhcp_cmd.set_text(self.confFile.get_opt('DHCP.command'))
1704 self.dhcp_cmd_label = gtk.Label("Command")
1705 self.dhcp_cmd_button = file_browse_button(self.dialog, self.dhcp_cmd)
1706 self.dhcp_table.attach(self.dhcp_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1707 self.dhcp_table.attach(self.dhcp_cmd, 2, 3, 1, 2, True, False, 0, 0)
1708 self.dhcp_table.attach(self.dhcp_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1710 self.dhcp_args = gtk.Entry()
1711 self.dhcp_args.set_width_chars(32)
1712 self.tooltips.set_tip(self.dhcp_args, "The start-up arguments to the DHCP command")
1713 self.dhcp_args.set_text(self.confFile.get_opt('DHCP.args'))
1714 self.dhcp_args_label = gtk.Label("Arguments")
1715 self.dhcp_table.attach(self.dhcp_args_label, 1, 2, 2, 3, True, False, 5, 0)
1716 self.dhcp_table.attach(self.dhcp_args, 2, 3, 2, 3, True, False, 0, 0)
1718 self.dhcp_kill_args = gtk.Entry()
1719 self.dhcp_kill_args.set_width_chars(32)
1720 self.tooltips.set_tip(self.dhcp_kill_args, "The shutdown arguments to the DHCP command")
1721 self.dhcp_kill_args.set_text(self.confFile.get_opt('DHCP.kill_args'))
1722 self.dhcp_kill_args_label = gtk.Label("Kill arguments")
1723 self.dhcp_table.attach(self.dhcp_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1724 self.dhcp_table.attach(self.dhcp_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1726 self.dhcp_timeout = gtk.Entry()
1727 self.dhcp_timeout.set_width_chars(32)
1728 self.tooltips.set_tip(self.dhcp_timeout, "The amount of time DHCP will spend trying to connect")
1729 self.dhcp_timeout.set_text(self.confFile.get_opt('DHCP.timeout'))
1730 self.dhcp_timeout_label = gtk.Label("DHCP connect timeout (seconds)")
1731 self.dhcp_table.attach(self.dhcp_timeout_label, 1, 2, 4, 5, True, False, 5, 0)
1732 self.dhcp_table.attach(self.dhcp_timeout, 2, 3, 4, 5, True, False, 0, 0)
1734 self.dhcp_pidfile = gtk.Entry()
1735 self.dhcp_pidfile.set_width_chars(32)
1736 self.tooltips.set_tip(self.dhcp_pidfile, "The file DHCP uses to store its PID")
1737 self.dhcp_pidfile.set_text(self.confFile.get_opt('DHCP.pidfile'))
1738 self.dhcp_pidfile_label = gtk.Label("PID file")
1739 self.dhcp_table.attach(self.dhcp_pidfile_label, 1, 2, 5, 6, True, False, 5, 0)
1740 self.dhcp_table.attach(self.dhcp_pidfile, 2, 3, 5, 6, True, False, 0, 0)
1742 self.prefs_notebook.append_page(self.dhcp_table, gtk.Label("DHCP"))
1743 ### End of DHCP tab
1745 ### WPA tab
1746 # table to use for layout of DHCP prefs
1747 self.wpa_table = gtk.Table()
1749 self.wpa_cmd = gtk.Entry()
1750 self.wpa_cmd.set_width_chars(32)
1751 self.tooltips.set_tip(self.wpa_cmd, "The command to use for WPA encrypted connections")
1752 self.wpa_cmd.set_text(self.confFile.get_opt('WPA.command'))
1753 self.wpa_cmd_label = gtk.Label("Command")
1754 self.wpa_cmd_button = file_browse_button(self.dialog, self.wpa_cmd)
1755 self.wpa_table.attach(self.wpa_cmd_label, 1, 2, 1, 2, True, False, 5, 0)
1756 self.wpa_table.attach(self.wpa_cmd, 2, 3, 1, 2, True, False, 0, 0)
1757 self.wpa_table.attach(self.wpa_cmd_button, 3, 4, 1, 2, False, False, 0, 0)
1759 self.wpa_args = gtk.Entry()
1760 self.wpa_args.set_width_chars(32)
1761 self.tooltips.set_tip(self.wpa_args, "The start-up arguments to the WPA command")
1762 self.wpa_args.set_text(self.confFile.get_opt('WPA.args'))
1763 self.wpa_args_label = gtk.Label("Arguments")
1764 self.wpa_table.attach(self.wpa_args_label, 1, 2, 2, 3, True, False, 5, 0)
1765 self.wpa_table.attach(self.wpa_args, 2, 3, 2, 3, True, False, 0, 0)
1767 self.wpa_kill_args = gtk.Entry()
1768 self.wpa_kill_args.set_width_chars(32)
1769 self.tooltips.set_tip(self.wpa_kill_args, "The shutdown arguments to the DHCP command")
1770 self.wpa_kill_args.set_text(self.confFile.get_opt('WPA.kill_command'))
1771 self.wpa_kill_args_label = gtk.Label("Kill command")
1772 self.wpa_table.attach(self.wpa_kill_args_label, 1, 2, 3, 4, True, False, 5, 0)
1773 self.wpa_table.attach(self.wpa_kill_args, 2, 3, 3, 4, True, False, 0, 0)
1775 self.wpa_config = gtk.Entry()
1776 self.wpa_config.set_width_chars(32)
1777 self.tooltips.set_tip(self.wpa_config, "The WPA configuration file to use")
1778 self.wpa_config.set_text(self.confFile.get_opt('WPA.configuration'))
1779 self.wpa_config_label = gtk.Label("Configuration file")
1780 self.wpa_table.attach(self.wpa_config_label, 1, 2, 4, 5, True, False, 5, 0)
1781 self.wpa_table.attach(self.wpa_config, 2, 3, 4, 5, True, False, 0, 0)
1783 self.wpa_driver = gtk.Entry()
1784 self.wpa_driver.set_width_chars(32)
1785 self.tooltips.set_tip(self.wpa_driver, "The WPA driver to use")
1786 self.wpa_driver.set_text(self.confFile.get_opt('WPA.driver'))
1787 self.wpa_driver_label = gtk.Label("Driver")
1788 self.wpa_table.attach(self.wpa_driver_label, 1, 2, 5, 6, True, False, 5, 0)
1789 self.wpa_table.attach(self.wpa_driver, 2, 3, 5, 6, True, False, 0, 0)
1791 self.wpa_pidfile = gtk.Entry()
1792 self.wpa_pidfile.set_width_chars(32)
1793 self.tooltips.set_tip(self.wpa_pidfile, "The file WPA uses to store its PID")
1794 self.wpa_pidfile.set_text(self.confFile.get_opt('WPA.pidfile'))
1795 self.wpa_pidfile_label = gtk.Label("PID file")
1796 self.wpa_table.attach(self.wpa_pidfile_label, 1, 2, 6, 7, True, False, 5, 0)
1797 self.wpa_table.attach(self.wpa_pidfile, 2, 3, 6, 7, True, False, 0, 0)
1799 self.prefs_notebook.append_page(self.wpa_table, gtk.Label("WPA"))
1800 ### End of WPA tab
1802 self.dialog.vbox.pack_start(self.prefs_notebook, False, False, 5)
1804 # Respond to Auto-detect checkbox toggle by activating/de-activating the interface combobox.
1806 #Parameters:
1808 # 'auto_detect_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1810 # 'data' -- tuple - list of arbitrary arguments (not used)
1812 #Returns:
1814 # nothing
1815 def toggle_auto_detect(self, auto_detect_toggle, data=None):
1816 self.w_interface.set_sensitive(not auto_detect_toggle.get_active())
1818 # Respond to Speak-up checkbox toggle by activating/de-activating the Speak Cmd Entry.
1820 #Parameters:
1822 # 'speak_toggle' -- gtk.CheckButton - The checkbox sending the signal.
1824 # 'data' -- tuple - list of arbitrary arguments (not used)
1826 #Returns:
1828 # nothing
1829 def toggle_speak(self, speak_toggle, data=None):
1830 self.w_speak_cmd.set_sensitive(speak_toggle.get_active())
1831 self.w_speak_cmd_button.set_sensitive(speak_toggle.get_active())
1833 # Display preferences dialog and operate until canceled or okayed.
1835 #Parameters:
1837 # nothing
1839 #Returns:
1841 # integer -- gtk response ID
1842 def run(self):
1843 self.dialog.show_all()
1844 return self.dialog.run()
1846 # Write updated values to config file.
1848 #Parameters:
1850 # nothing
1852 #Returns:
1854 # nothing
1855 def save(self):
1856 if self.w_auto_detect.get_active():
1857 set_network_device("auto_detect")
1858 self.confFile.set_opt('DEFAULT.interface', "auto_detect")
1859 else:
1860 # get the selected interface, using a work-around suggested by PyGTK Tutorial
1861 interface = self.w_interface.get_model()[self.w_interface.get_active()][0]
1862 self.confFile.set_opt('DEFAULT.interface', interface)
1863 set_network_device(interface)
1864 #self.confFile.set_float_opt('DEFAULT.scan_timeout', self.w_scan_timeout.get_value())
1865 self.confFile.set_bool_opt('DEFAULT.speak_up', self.w_speak_up.get_active())
1866 self.confFile.set_bool_opt('DEFAULT.commit_required', self.w_commit_required.get_active())
1867 self.confFile.set_bool_opt('DEFAULT.ifup_required', self.w_ifup_required.get_active())
1868 self.confFile.set_opt('DEFAULT.speak_command', self.w_speak_cmd.get_text())
1869 self.confFile.set_opt('DEFAULT.ifconfig_command', self.ifconfig_cmd.get_text())
1870 self.confFile.set_opt('DEFAULT.iwconfig_command', self.iwconfig_cmd.get_text())
1871 self.confFile.set_opt('DEFAULT.iwlist_command', self.iwlist_cmd.get_text())
1872 self.confFile.set_opt('DEFAULT.route_command', self.route_cmd.get_text())
1873 self.confFile.set_opt('DEFAULT.logfile', self.logfile_entry.get_text())
1874 self.confFile.set_int_opt('DEFAULT.loglevel', int(self.loglevel.get_value()))
1875 self.confFile.set_opt('DHCP.command', self.dhcp_cmd.get_text())
1876 self.confFile.set_opt('DHCP.args', self.dhcp_args.get_text())
1877 self.confFile.set_opt('DHCP.kill_args', self.dhcp_kill_args.get_text())
1878 self.confFile.set_opt('DHCP.timeout', self.dhcp_timeout.get_text())
1879 self.confFile.set_opt('DHCP.pidfile', self.dhcp_pidfile.get_text())
1880 self.confFile.set_opt('WPA.command', self.wpa_cmd.get_text())
1881 self.confFile.set_opt('WPA.args', self.wpa_args.get_text())
1882 self.confFile.set_opt('WPA.kill_command', self.wpa_kill_args.get_text())
1883 self.confFile.set_opt('WPA.configuration', self.wpa_config.get_text())
1884 self.confFile.set_opt('WPA.driver', self.wpa_driver.get_text())
1885 self.confFile.set_opt('WPA.pidfile', self.wpa_pidfile.get_text())
1886 try:
1887 self.confFile.write()
1888 except IOError, (error_number, error_str):
1889 if error_number == errno.ENOENT:
1890 error_dlg = ErrorDialog( self.window, "Could not save configuration file:\n%s\n\n%s" % (self.confFile.filename, error_str) )
1891 del error_dlg
1892 else:
1893 raise IOError(error_number, error_str)
1895 # Remove preferences window.
1897 #Parameters:
1899 # nothing
1901 #Returns:
1903 # nothing
1904 def destroy(self):
1905 self.dialog.destroy()
1906 del self.dialog
1909 # Edit and return an AP profile.
1910 class profile_dialog:
1911 # Create a new profile_dialog.
1913 #Parameters:
1915 # 'parent' -- gtk.Object - Usually, the calling window.
1917 # 'profile' -- dictionary - The profile to edit. May be mostly-empty default profile.
1919 #Returns:
1921 # profile_dialog instance
1922 def __init__( self, parent, profile ):
1923 global wifi_radar_icon
1925 # Labels
1926 self.WIFI_SET_LABEL = "WiFi Options"
1927 self.USE_DHCP_LABEL = "Automatic network configuration (DHCP)"
1928 self.USE_IP_LABEL = "Manual network configuration"
1929 self.USE_WPA_LABEL = "Use WPA"
1930 self.NO_WPA_LABEL = "No WPA"
1931 self.CON_PP_LABEL = "Connection Commands"
1932 self.DIS_PP_LABEL = "Disconnection Commands"
1934 self.parent = parent
1935 self.profile = profile.copy()
1936 self.WIFI_MODES = [ '', 'auto', 'Managed', 'Ad-Hoc', 'Master', 'Repeater', 'Secondary', 'Monitor' ]
1937 self.WIFI_SECURITY = [ '', 'open', 'restricted' ]
1938 self.WIFI_CHANNELS = [ '', 'auto', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14' ]
1939 self.dialog = gtk.Dialog('WiFi Profile', self.parent.window,
1940 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
1941 ( gtk.STOCK_CANCEL, False, gtk.STOCK_SAVE, True ) )
1942 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
1943 self.dialog.set_icon( icon )
1944 self.dialog.set_resizable( False )
1945 self.dialog.set_transient_for( self.parent.window )
1946 #self.dialog.set_size_request( 400, 400 )
1947 #################
1948 self.tooltips = gtk.Tooltips()
1950 general_table = gtk.Table()
1951 general_table.set_row_spacings(3)
1952 general_table.set_col_spacings(3)
1953 # The essid labels
1954 general_table.attach(gtk.Label('Network Name'), 0, 1, 0, 1)
1955 # The essid textboxes
1956 self.essid_entry = gtk.Entry(32)
1957 self.essid_entry.set_text(self.profile['essid'])
1958 general_table.attach(self.essid_entry, 1, 2, 0, 1)
1959 # Add the essid table to the dialog
1960 self.dialog.vbox.pack_start(general_table, True, True, 5)
1961 self.tooltips.set_tip(self.essid_entry, "The name of the network with which to connect.")
1963 # The bssid labels
1964 general_table.attach(gtk.Label('Network Address'), 0, 1, 1, 2)
1965 # The bssid textboxes
1966 self.bssid_entry = gtk.Entry(32)
1967 self.bssid_entry.set_text(self.profile['bssid'])
1968 self.bssid_entry.set_sensitive(not self.profile['roaming'])
1969 # Add the bssid table to the dialog
1970 general_table.attach(self.bssid_entry, 1, 2, 1, 2)
1971 self.tooltips.set_tip(self.bssid_entry, "The address of the network with which to connect.")
1972 # Add the roaming checkbox
1973 self.roaming_cb = gtk.CheckButton('Roaming')
1974 self.roaming_cb.set_active(self.profile['roaming'])
1975 self.roaming_cb.connect("toggled", self.toggle_roaming)
1976 general_table.attach(self.roaming_cb, 1, 2, 2, 3)
1977 self.tooltips.set_tip(self.roaming_cb, "Use the AP in this network which provides strongest signal?")
1978 # create the WiFi expander
1979 self.wifi_expander = gtk.Expander( self.WIFI_SET_LABEL )
1980 #self.wifi_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
1981 wifi_table = gtk.Table( 4, 2, False )
1982 wifi_table.set_row_spacings( 3 )
1983 wifi_table.set_col_spacings( 3 )
1984 # The WiFi labels
1985 wifi_table.attach( gtk.Label( 'Mode' ), 0, 1, 0, 1 )
1986 wifi_table.attach( gtk.Label( 'Channel' ), 0, 1, 1, 2 )
1987 wifi_table.attach( gtk.Label( 'Key' ), 0, 1, 2, 3 )
1988 wifi_table.attach( gtk.Label( 'Security' ), 0, 1, 3, 4 )
1989 # The WiFi text boxes
1990 self.mode_combo = gtk.combo_box_new_text()
1991 for mode in self.WIFI_MODES:
1992 self.mode_combo.append_text( mode )
1993 self.mode_combo.set_active( self.get_array_index( self.profile['mode'], self.WIFI_MODES ) )
1994 wifi_table.attach( self.mode_combo, 1, 2, 0, 1 )
1995 self.tooltips.set_tip(self.mode_combo, "Method to use for connection. You probably want auto mode.")
1996 self.channel_combo = gtk.combo_box_new_text()
1997 for channel in self.WIFI_CHANNELS:
1998 self.channel_combo.append_text( channel )
1999 self.channel_combo.set_active( self.get_array_index( self.profile['channel'], self.WIFI_CHANNELS ) )
2000 wifi_table.attach( self.channel_combo, 1, 2, 1, 2 )
2001 self.tooltips.set_tip(self.channel_combo, "Channel the network uses. You probably want auto mode.")
2003 self.key_entry = gtk.Entry( 64 )
2004 self.key_entry.set_text( self.profile['key'] )
2005 wifi_table.attach( self.key_entry, 1, 2, 2, 3 )
2006 self.tooltips.set_tip(self.key_entry, "WEP key: Plain language or hex string to use for encrypted communication with the network.")
2008 self.security_combo = gtk.combo_box_new_text()
2009 for security in self.WIFI_SECURITY:
2010 self.security_combo.append_text( security )
2011 self.security_combo.set_active( self.get_array_index( self.profile['security'], self.WIFI_SECURITY ) )
2012 wifi_table.attach( self.security_combo, 1, 2, 3, 4 )
2013 self.tooltips.set_tip(self.security_combo, "Use Open to allow unencrypted communication and Restricted to force encrypted-only communication.")
2014 # Add the wifi table to the expander
2015 self.wifi_expander.add( wifi_table )
2016 # Add the expander to the dialog
2017 self.dialog.vbox.pack_start( self.wifi_expander, False, False, 5 )
2019 # create the wpa expander
2020 self.wpa_expander = gtk.Expander( self.NO_WPA_LABEL )
2021 self.wpa_expander.connect( 'notify::expanded', self.toggle_use_wpa )
2022 wpa_table = gtk.Table( 1, 2, False )
2023 wpa_table.set_row_spacings( 3 )
2024 wpa_table.set_col_spacings( 3 )
2025 # The labels
2026 wpa_table.attach( gtk.Label( 'Driver' ), 0, 1, 0, 1 )
2027 # The text boxes
2028 self.wpa_driver_entry = gtk.Entry()
2029 self.wpa_driver_entry.set_text( self.profile['wpa_driver'] )
2030 wpa_table.attach( self.wpa_driver_entry, 1, 2, 0, 1 )
2031 # Add the wpa table to the expander
2032 self.wpa_expander.add( wpa_table )
2033 # Add the expander to the dialog
2034 self.dialog.vbox.pack_start( self.wpa_expander, False, False, 5 )
2036 # create the dhcp expander
2037 self.dhcp_expander = gtk.Expander( self.USE_DHCP_LABEL )
2038 self.dhcp_expander.connect( 'notify::expanded', self.toggle_use_dhcp )
2039 ip_table = gtk.Table( 6, 2, False )
2040 ip_table.set_row_spacings( 3 )
2041 ip_table.set_col_spacings( 3 )
2042 # The IP labels
2043 ip_table.attach( gtk.Label( 'IP' ), 0, 1, 0, 1 )
2044 ip_table.attach( gtk.Label( 'Netmask' ), 0, 1, 1, 2 )
2045 ip_table.attach( gtk.Label( 'Gateway' ), 0, 1, 2, 3 )
2046 ip_table.attach( gtk.Label( 'Domain' ), 0, 1, 3, 4 )
2047 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 4, 5 )
2048 ip_table.attach( gtk.Label( 'DNS' ), 0, 1, 5, 6 )
2049 # The IP text boxes
2050 self.ip_entry = gtk.Entry( 15 )
2051 self.ip_entry.set_text( self.profile['ip'] )
2052 ip_table.attach( self.ip_entry, 1, 2, 0, 1 )
2053 self.netmask_entry = gtk.Entry( 15 )
2054 self.netmask_entry.set_text( self.profile['netmask'] )
2055 ip_table.attach( self.netmask_entry, 1, 2, 1, 2 )
2056 self.gw_entry = gtk.Entry( 15 )
2057 self.gw_entry.set_text( self.profile['gateway'] )
2058 ip_table.attach( self.gw_entry, 1, 2, 2, 3 )
2059 self.domain_entry = gtk.Entry( 32 )
2060 self.domain_entry.set_text( self.profile['domain'] )
2061 ip_table.attach( self.domain_entry, 1, 2, 3, 4 )
2062 self.dns1_entry = gtk.Entry( 15 )
2063 self.dns1_entry.set_text( self.profile['dns1'] )
2064 ip_table.attach( self.dns1_entry, 1, 2, 4, 5 )
2065 self.dns2_entry = gtk.Entry( 15 )
2066 self.dns2_entry.set_text( self.profile['dns2'] )
2067 ip_table.attach( self.dns2_entry, 1, 2, 5, 6 )
2068 # Add the ip table to the expander
2069 self.dhcp_expander.add( ip_table )
2070 # Add the expander to the dialog
2071 self.dialog.vbox.pack_start( self.dhcp_expander, False, False, 5 )
2073 # create the connection-building postpre expander
2074 self.con_pp_expander = gtk.Expander( self.CON_PP_LABEL )
2075 con_pp_table = gtk.Table( 2, 2, False )
2076 con_pp_table.set_row_spacings( 3 )
2077 con_pp_table.set_col_spacings( 3 )
2078 # The labels
2079 con_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2080 con_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2081 # The text boxes
2082 self.con_prescript_entry = gtk.Entry()
2083 self.con_prescript_entry.set_text( self.profile['con_prescript'] )
2084 con_pp_table.attach( self.con_prescript_entry, 1, 2, 0, 1 )
2085 self.tooltips.set_tip(self.con_prescript_entry, "The local command to execute before trying to connect to the network.")
2086 self.con_postscript_entry = gtk.Entry()
2087 self.con_postscript_entry.set_text( self.profile['con_postscript'] )
2088 con_pp_table.attach( self.con_postscript_entry, 1, 2, 1, 2 )
2089 self.tooltips.set_tip(self.con_postscript_entry, "The local command to execute after connecting to the network.")
2090 # Add the pp table to the expander
2091 self.con_pp_expander.add( con_pp_table )
2092 # Add the expander to the dialog
2093 self.dialog.vbox.pack_start( self.con_pp_expander, False, False, 5 )
2095 # create the disconnection postpre expander
2096 self.dis_pp_expander = gtk.Expander( self.DIS_PP_LABEL )
2097 dis_pp_table = gtk.Table( 2, 2, False )
2098 dis_pp_table.set_row_spacings( 3 )
2099 dis_pp_table.set_col_spacings( 3 )
2100 # The labels
2101 dis_pp_table.attach( gtk.Label( 'Before' ), 0, 1, 0, 1 )
2102 dis_pp_table.attach( gtk.Label( 'After' ), 0, 1, 1, 2 )
2103 # The text boxes
2104 self.dis_prescript_entry = gtk.Entry()
2105 self.dis_prescript_entry.set_text( self.profile['dis_prescript'] )
2106 dis_pp_table.attach( self.dis_prescript_entry, 1, 2, 0, 1 )
2107 self.tooltips.set_tip(self.dis_prescript_entry, "The local command to execute before disconnecting from the network.")
2108 self.dis_postscript_entry = gtk.Entry()
2109 self.dis_postscript_entry.set_text( self.profile['dis_postscript'] )
2110 dis_pp_table.attach( self.dis_postscript_entry, 1, 2, 1, 2 )
2111 self.tooltips.set_tip(self.dis_postscript_entry, "The local command to execute after disconnecting from the network.")
2112 # Add the pp table to the expander
2113 self.dis_pp_expander.add( dis_pp_table )
2114 # Add the expander to the dialog
2115 self.dialog.vbox.pack_start( self.dis_pp_expander, False, False, 5 )
2117 # Display profile dialog, operate until canceled or okayed, and, possibly, return edited profile values.
2119 #Parameters:
2121 # nothing
2123 #Returns:
2125 # dictionary or None -- a profile, or None on cancel
2127 #NOTES:
2129 # Raises ValueError if an attempt is made to save an ESSID with no name.
2130 def run( self ):
2131 self.dialog.show_all()
2132 if self.dialog.run():
2133 if self.essid_entry.get_text().strip() == "":
2134 raise ValueError
2135 self.profile['known'] = True
2136 self.profile['essid'] = self.essid_entry.get_text().strip()
2137 if self.roaming_cb.get_active():
2138 self.profile['bssid'] = ''
2139 else:
2140 self.profile['bssid'] = self.bssid_entry.get_text().strip()
2141 self.profile['roaming'] = self.roaming_cb.get_active()
2142 self.profile['key'] = self.key_entry.get_text().strip()
2143 self.profile['mode'] = self.get_array_item( self.mode_combo.get_active(), self.WIFI_MODES )
2144 self.profile['security'] = self.get_array_item( self.security_combo.get_active(), self.WIFI_SECURITY )
2145 self.profile['encrypted'] = ( self.profile['security'] != '' )
2146 self.profile['channel'] = self.get_array_item( self.channel_combo.get_active(), self.WIFI_CHANNELS )
2147 self.profile['protocol'] = 'g'
2148 self.profile['available'] = ( self.profile['signal'] > 0 )
2149 self.profile['con_prescript'] = self.con_prescript_entry.get_text().strip()
2150 self.profile['con_postscript'] = self.con_postscript_entry.get_text().strip()
2151 self.profile['dis_prescript'] = self.dis_prescript_entry.get_text().strip()
2152 self.profile['dis_postscript'] = self.dis_postscript_entry.get_text().strip()
2153 # wpa
2154 self.profile['use_wpa'] = self.wpa_expander.get_expanded()
2155 self.profile['wpa_driver'] = self.wpa_driver_entry.get_text().strip()
2156 # dhcp
2157 self.profile['use_dhcp'] = not self.dhcp_expander.get_expanded()
2158 self.profile['ip'] = self.ip_entry.get_text().strip()
2159 self.profile['netmask'] = self.netmask_entry.get_text().strip()
2160 self.profile['gateway'] = self.gw_entry.get_text().strip()
2161 self.profile['domain'] = self.domain_entry.get_text().strip()
2162 self.profile['dns1'] = self.dns1_entry.get_text().strip()
2163 self.profile['dns2'] = self.dns2_entry.get_text().strip()
2164 return self.profile
2165 return None
2167 # Remove profile dialog.
2169 #Parameters:
2171 # nothing
2173 #Returns:
2175 # nothing
2176 def destroy( self ):
2177 self.dialog.destroy()
2178 del self.dialog
2180 # Respond to roaming checkbox toggle by activating/de-activating the BSSID text entry.
2182 #Parameters:
2184 # 'roaming_toggle' -- gtk.CheckButton - The checkbox sending the signal.
2186 # 'data' -- tuple - list of arbitrary arguments (not used)
2188 #Returns:
2190 # nothing
2191 def toggle_roaming(self, roaming_toggle, data=None):
2192 self.bssid_entry.set_sensitive(not roaming_toggle.get_active())
2194 # Respond to expanding/hiding IP segment.
2196 #Parameters:
2198 # 'widget' -- gtk.Widget - The widget sending the event.
2200 # 'data' -- tuple - List of arbitrary arguments (not used)
2202 #Returns:
2204 # nothing
2205 def toggle_use_dhcp( self, widget, data = None ):
2206 expanded = self.dhcp_expander.get_expanded()
2207 if expanded:
2208 self.dhcp_expander.set_label( self.USE_IP_LABEL )
2209 else:
2210 self.dhcp_expander.set_label( self.USE_DHCP_LABEL )
2212 # Respond to expanding/hiding WPA segment.
2214 #Parameters:
2216 # 'widget' -- gtk.Widget - The widget sending the event.
2218 # 'data' -- tuple - List of arbitrary arguments (not used)
2220 #Returns:
2222 # nothing
2223 def toggle_use_wpa( self, widget, data = None ):
2224 expanded = self.wpa_expander.get_expanded()
2225 if expanded:
2226 self.wpa_expander.set_label( self.USE_WPA_LABEL )
2227 else:
2228 self.wpa_expander.set_label( self.NO_WPA_LABEL )
2230 # Return the index where item matches a cell in array.
2232 #Parameters:
2234 # 'item' -- string - Item to find in array
2236 # 'array' -- list - List in which to find match.
2238 #Returns:
2240 # integer - 0 (no match) or higher (index of match)
2241 def get_array_index( self, item, array ):
2242 try:
2243 return array.index( item.strip() )
2244 except:
2245 pass
2246 return 0
2248 # Return the value in array[ index ]
2250 #Parameters:
2252 # 'index' -- integer - The index to look up.
2254 # 'array' -- list - List in which to look up value.
2256 #Returns:
2258 # string -- empty string (no match) or looked up value
2259 def get_array_item( self, index, array ):
2260 try:
2261 return array[ index ]
2262 except:
2263 pass
2264 return ''
2267 # A simple class for putting up a "Please wait" dialog so the user
2268 # doesn't think we've forgotten about them. Implements the status interface.
2269 class StatusWindow:
2270 # Create a new StatusWindow.
2272 #Parameters:
2274 # 'parent' -- gtk.Object - Usually, the calling window.
2276 #Returns:
2278 # StatusWindow instance
2280 #NOTE:
2282 # Sample implementation of status interface. Status interface
2283 #requires .show(), .update_message(message), and .hide() methods.
2284 def __init__( self, parent ):
2285 global wifi_radar_icon
2286 self.parent = parent
2287 self.dialog = gtk.Dialog("Working", self.parent.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
2288 icon = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2289 self.dialog.set_icon( icon )
2290 self.lbl = gtk.Label("Please wait...")
2291 self.bar = gtk.ProgressBar()
2292 self.dialog.vbox.pack_start(self.lbl)
2293 self.dialog.vbox.pack_start(self.bar)
2294 self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.BUTTONS_CANCEL)
2295 self.timer = None
2297 # Change the message displayed to the user.
2299 #Parameters:
2301 # 'message' -- string - The message to show to the user.
2303 #Returns:
2305 # nothing
2306 def update_message( self, message ):
2307 self.lbl.set_text(message)
2309 # Update the StatusWindow progress bar.
2311 #Parameters:
2313 # nothing
2315 #Returns:
2317 # True -- always return True
2318 def update_window( self ):
2319 self.bar.pulse()
2320 return True
2322 # Display and operate the StatusWindow.
2324 #Parameters:
2326 # nothing
2328 #Returns:
2330 # nothing
2331 def run( self ):
2332 pass
2334 # Show all the widgets of the StatusWindow.
2336 #Parameters:
2338 # nothing
2340 #Returns:
2342 # nothing
2343 def show( self ):
2344 self.dialog.show_all()
2345 self.timer = gobject.timeout_add(250, self.update_window)
2346 return False
2348 # Hide all the widgets of the StatusWindow.
2350 #Parameters:
2352 # nothing
2354 #Returns:
2356 # nothing
2357 def hide( self ):
2358 if self.timer:
2359 gobject.source_remove(self.timer)
2360 self.timer = None
2361 self.dialog.hide_all()
2362 return False
2364 # Remove the StatusWindow.
2366 #Parameters:
2368 # nothing
2370 #Returns:
2372 # nothing
2373 def destroy( self ):
2374 if self.timer:
2375 gobject.source_remove(self.timer)
2376 self.dialog.destroy()
2377 del self.dialog
2380 # Manage a GTK About Dialog
2381 class about_dialog(gtk.AboutDialog):
2382 # Subclass GTK AboutDialog
2384 #Parameters:
2386 # nothing
2388 #Returns:
2390 # nothing
2391 def __init__( self ):
2392 global wifi_radar_icon
2394 gtk.AboutDialog.__init__(self)
2395 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"])
2396 self.set_comments("WiFi connection manager")
2397 self.set_copyright("Copyright 2004-2009 by various authors and contributors\nCurrent Maintainer: Sean Robinson <seankrobinson@gmail.com>")
2398 self.set_documenters(["Gary Case"])
2399 license = """
2400 This program is free software; you can redistribute it and/or modify
2401 it under the terms of the GNU General Public License as published by
2402 the Free Software Foundation; either version 2 of the License, or
2403 (at your option) any later version.
2405 This program is distributed in the hope that it will be useful,
2406 but WITHOUT ANY WARRANTY; without even the implied warranty of
2407 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2408 GNU General Public License for more details.
2410 You should have received a copy of the GNU General Public License
2411 along with this program; if not, write to the Free Software
2412 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
2413 self.set_license(license)
2414 logo = gtk.gdk.pixbuf_new_from_inline( len( wifi_radar_icon[0] ), wifi_radar_icon[0], False )
2415 self.set_logo(logo)
2416 self.set_name("WiFi Radar")
2417 self.set_version(WIFI_RADAR_VERSION)
2418 self.set_website("http://wifi-radar.berlios.de")
2422 # Manage the configuration for the application, including reading and writing the config from/to a file.
2423 class ConfigFile(ConfigParser.SafeConfigParser):
2424 # Create a new ConfigFile.
2426 #Parameters:
2428 # 'filename' -- string - The configuration file's name.
2430 # 'defaults' -- dictionary - Default values for the DEFAULT section.
2432 #Returns:
2434 # ConfigFile instance
2435 def __init__( self, filename, defaults, raw=False ):
2436 self.filename = filename
2437 self.raw = raw
2438 self.auto_profile_order = []
2439 ConfigParser.SafeConfigParser.__init__(self, defaults)
2441 # Set the contents of a section to values from a dictionary.
2443 #Parameters:
2445 # 'section_name' -- string - Configuration file section.
2447 # 'section_dict' -- dictionary - Values to add to section.
2449 #Returns:
2451 # nothing
2452 def set_section( self, section_name, section_dict ):
2453 try:
2454 self.add_section(section_name)
2455 except ConfigParser.DuplicateSectionError:
2456 pass
2457 for key in section_dict.keys():
2458 if type(section_dict[key]) == BooleanType:
2459 self.set_bool_opt(section_name + "." + key, section_dict[key])
2460 elif type(section_dict[key]) == IntType:
2461 self.set_int_opt(section_name + "." + key, section_dict[key])
2462 elif type(section_dict[key]) == FloatType:
2463 self.set_float_opt(section_name + "." + key, section_dict[key])
2464 else:
2465 self.set_opt(section_name + "." + key, section_dict[key])
2467 # Return the profile recorded in the specified section.
2469 #Parameters:
2471 # 'section_name' -- string - Configuration file section.
2473 #Returns:
2475 # dictionary or None - The specified profile or None if not found
2476 def get_profile( self, section_name ):
2477 if section_name in self.profiles():
2478 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' ]
2479 bool_types = [ 'known', 'available', 'roaming', 'encrypted', 'use_wpa', 'use_dhcp' ]
2480 int_types = [ 'signal' ]
2481 profile = get_new_profile()
2482 for option in bool_types:
2483 option_tmp = self.get_opt_as_bool(section_name + "." + option)
2484 if option_tmp:
2485 profile[option] = option_tmp
2486 for option in int_types:
2487 option_tmp = self.get_opt_as_int(section_name + "." + option)
2488 if option_tmp:
2489 profile[option] = option_tmp
2490 for option in str_types:
2491 option_tmp = self.get_opt(section_name + "." + option)
2492 if option_tmp:
2493 profile[option] = option_tmp
2494 return profile
2495 return None
2497 # Get a config option and handle exceptions.
2499 #Parameters:
2501 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2502 # period and the option key. (E.g. "DEFAULT.interface")
2504 #Returns:
2506 # string or None - option value as string or None on failure
2507 def get_opt( self, option_path ):
2508 #print "ConfigFile.get_opt: ", option_path
2509 (section, option) = option_path.split('.')
2510 try:
2511 return self.get(section, option, self.raw)
2512 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
2513 return None
2515 # Get a config option and return as a boolean type.
2517 #Parameters:
2519 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2520 # period and the option key. (E.g. "DEFAULT.interface")
2522 #Returns:
2524 # boolean - option value as boolean
2525 def get_opt_as_bool( self, option_path ):
2526 option = self.get_opt(option_path)
2527 if isinstance(option, BooleanType) or isinstance(option, NoneType):
2528 return option
2529 if option == 'True':
2530 return True
2531 if option == 'False':
2532 return False
2533 raise ValueError, 'boolean option was not True or False'
2535 # Get a config option and return as an integer type.
2537 #Parameters:
2539 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2540 # period and the option key. (E.g. "DEFAULT.interface")
2542 #Returns:
2544 # integer- option value as integer
2545 def get_opt_as_int( self, option_path ):
2546 return int(float(self.get_opt(option_path)))
2548 # Convert boolean type to string and set config option.
2550 #Parameters:
2552 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2553 # period and the option key. (E.g. "DEFAULT.interface")
2555 # 'value' -- boolean - Value to set.
2557 #Returns:
2559 # nothing
2560 def set_bool_opt( self, option_path, value ):
2561 if ( value == True ) or ( value > 0 ) or ( value == 'True' ):
2562 value == 'True'
2563 elif ( value == False ) or ( value == 0 ) or ( value == 'False' ):
2564 value == 'False'
2565 else:
2566 raise ValueError, 'cannot convert value to string'
2567 self.set_opt(option_path, repr(value))
2569 # Convert integer type to string and set config option.
2571 #Parameters:
2573 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2574 # period and the option key. (E.g. "DEFAULT.interface")
2576 # 'value' -- integer - Value to set.
2578 #Returns:
2580 # nothing
2581 def set_int_opt( self, option_path, value ):
2582 if not isinstance(value, IntType):
2583 raise ValueError, 'value is not an integer'
2584 self.set_opt(option_path, repr(value))
2586 # Convert float type to string and set config option.
2588 #Parameters:
2590 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2591 # period and the option key. (E.g. "DEFAULT.interface")
2593 # 'value' -- float - Value to set.
2595 #Returns:
2597 # nothing
2598 def set_float_opt( self, option_path, value ):
2599 if not isinstance(value, FloatType):
2600 raise ValueError, 'value is not a float'
2601 self.set_opt(option_path, repr(int(value)))
2603 # Set a config option while handling exceptions.
2605 #Parameters:
2607 # 'option_path' -- string - Section (a.k.a. profile) name concatenated with a
2608 # period and the option key. (E.g. "DEFAULT.interface")
2610 # 'value' -- string - Value to set.
2612 #Returns:
2614 # nothing
2615 def set_opt( self, option_path, value ):
2616 (section, option) = option_path.split('.')
2617 try:
2618 self.set(section, option, value)
2619 except ConfigParser.NoSectionError:
2620 self.add_section(section)
2621 self.set_opt(option_path, value)
2623 # Return a list of the section names which denote AP profiles.
2625 #Parameters:
2627 # nothing
2629 #Returns:
2631 # list - profile names
2632 def profiles( self ):
2633 profile_list = []
2634 for section in self.sections():
2635 if ':' in section:
2636 profile_list.append(section)
2637 return profile_list
2639 # Read configuration file from disk into instance variables.
2641 #Parameters:
2643 # nothing
2645 #Returns:
2647 # nothing
2648 def read( self ):
2649 fp = open( self.filename, "r" )
2650 self.readfp(fp)
2651 # convert the auto_profile_order to a list for ordering
2652 self.auto_profile_order = eval(self.get_opt('DEFAULT.auto_profile_order'))
2653 for ap in self.profiles():
2654 self.set_bool_opt( ap + '.known', True)
2655 if ap in self.auto_profile_order: continue
2656 self.auto_profile_order.append( ap )
2657 fp.close()
2659 # Write configuration file to disk from instance variables. Copied from
2660 # ConfigParser and modified to write options in alphabetical order.
2662 #Parameters:
2664 # nothing
2666 #Returns:
2668 # nothing
2669 def write( self ):
2670 self.set_opt('DEFAULT.auto_profile_order', str(self.auto_profile_order))
2671 self.set_opt('DEFAULT.version', WIFI_RADAR_VERSION)
2672 fp = open( self.filename, "w" )
2673 # write DEFAULT section first
2674 if self._defaults:
2675 fp.write("[DEFAULT]\n")
2676 for key in sorted(self._defaults.keys()):
2677 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
2678 fp.write("\n")
2679 # write non-profile sections first
2680 for section in self._sections:
2681 if section not in self.profiles():
2682 fp.write("[%s]\n" % section)
2683 for key in sorted(self._sections[section].keys()):
2684 if key != "__name__":
2685 fp.write("%s = %s\n" %
2686 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2687 fp.write("\n")
2688 # write profile sections
2689 for section in self._sections:
2690 if section in self.profiles():
2691 fp.write("[%s]\n" % section)
2692 for key in sorted(self._sections[section].keys()):
2693 if key != "__name__":
2694 fp.write("%s = %s\n" %
2695 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
2696 fp.write("\n")
2697 fp.close()
2701 # Load our conf file and known profiles
2702 # Defaults, these may get overridden by values found in the conf file.
2703 config_defaults = { # The network interface you use.
2704 # Specify "auto_detect" as the interface to make wifi-radar automatically detect your wireless card.
2705 'interface': "auto_detect",
2706 # How long should the scan for access points last?
2707 #'scan_timeout': '5',
2708 # X1000 Linux has a say command (text to speech) to announce connecting to networks.
2709 # Set the speak_up option to false if you do not have or want this.
2710 'speak_command': '/usr/bin/say',
2711 # Should I speak up when connecting to a network? (If you have a speech command)
2712 'speak_up': 'False',
2713 # You may set this to true for cards that require a "commit" command with iwconfig
2714 'commit_required': 'False',
2715 # You may set this to true for cards that require the interface to be brought up first
2716 'ifup_required': 'False',
2717 # set the location and verbosity of the log file
2718 'logfile': '/var/log/wifi-radar.log',
2719 'loglevel': '50',
2720 # Set the location of several important programs
2721 'iwlist_command': '/sbin/iwlist',
2722 'iwconfig_command': '/sbin/iwconfig',
2723 'ifconfig_command': '/sbin/ifconfig',
2724 'route_command': '/sbin/route',
2725 'auto_profile_order': '[]',
2726 'version': WIFI_RADAR_VERSION }
2728 config_dhcp = { # DHCP client
2729 'command': '/sbin/dhcpcd',
2730 # How long to wait for an IP addr from DHCP server
2731 'timeout': '30',
2732 # Arguments to use with DHCP client on connect
2733 'args': '-D -o -i dhcp_client -t %(timeout)s',
2734 # Argument to use with DHCP client on disconnect
2735 'kill_args': '-k',
2736 # The file where DHCP client PID is written
2737 'pidfile': '/etc/dhcpc/dhcpcd-%(interface)s.pid' }
2739 config_wpa = { # WPA Supplicant
2740 'command': '/usr/sbin/wpa_supplicant',
2741 # Arguments to use with WPA Supplicant on connect
2742 'args': '-B -i %(interface)s -c %(configuration)s -D %(driver)s -P %(pidfile)s',
2743 # Arguments to use with WPA Supplicant on disconnect
2744 'kill_command': '',
2745 # Where the WPA Supplicant config file can be found
2746 'configuration': '/etc/wpa_supplicant.conf',
2747 # Driver to use with WPA Supplicant
2748 'driver': 'wext',
2749 # The file where WPA Supplicant PID is written
2750 'pidfile': '/var/run/wpa_supplicant.pid' }
2752 # initialize config, with defaults
2753 confFile = ConfigFile(CONF_FILE, config_defaults)
2754 confFile.set_section("DHCP", config_dhcp)
2755 confFile.set_section("WPA", config_wpa)
2757 if not os.path.isfile( CONF_FILE ):
2758 confFile.set_bool_opt('DEFAULT.new_file', True)
2759 else:
2760 if not os.access(CONF_FILE, os.R_OK):
2761 print "Can't open " + CONF_FILE + "."
2762 print "Are you root?"
2763 sys.exit()
2764 confFile.read()
2767 ####################################################################################################
2768 # Embedded Images
2769 wifi_radar_icon = [ ""
2770 "GdkP"
2771 "\0\0\22""7"
2772 "\2\1\0\2"
2773 "\0\0\1\214"
2774 "\0\0\0c"
2775 "\0\0\0O"
2776 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377"
2777 "\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\261\377\377"
2778 "\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"
2779 "\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"
2780 "\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"
2781 "\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"
2782 "\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"
2783 "\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"
2784 "\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"
2785 "\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"
2786 "\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"
2787 "\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"
2788 "\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"
2789 "\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"
2790 "\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"
2791 "\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"
2792 "\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"
2793 "\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"
2794 "\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"
2795 "\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"
2796 "\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"
2797 "\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"
2798 "\0\0\272\214\0\0\0\377\3\0\0\0\375\0\0\0\241\0\0\0\"\226\377\377\377"
2799 "\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"
2800 "\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"
2801 "\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"
2802 "\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"
2803 "\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"
2804 "\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"
2805 "\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"
2806 "\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"
2807 "\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"
2808 "\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"
2809 "\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"
2810 "\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"
2811 "\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"
2812 "\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"
2813 "\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"
2814 "\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"
2815 "\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"
2816 "\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"
2817 "\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"
2818 "\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"
2819 "\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"
2820 "\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"
2821 "\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"
2822 "\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"
2823 "\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"
2824 "\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"
2825 "\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"
2826 "\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"
2827 "\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"
2828 "\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"
2829 "\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"
2830 "\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"
2831 "\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"
2832 "\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"
2833 "\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"
2834 "\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"
2835 "\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"
2836 "\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"
2837 "\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"
2838 "\377\377\377\0\1\0\0\0a\211\0\0\0\377\1\0\0\0\257\232\377\377\377\0\1"
2839 "\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"
2840 "\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"
2841 "*\0\0\0\242\211\0\0\0\310\1\0\0\0`\205\377\377\377\0\1\0\0\0\276\211"
2842 "\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"
2843 "\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"
2844 "\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"
2845 "\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"
2846 "\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"
2847 "\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"
2848 "\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"
2849 "\377\377\377\0\1\0\0\0|\211\0\0\0\377\1\0\0\0""0\226\377\377\377\0\2"
2850 "\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"
2851 "\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"
2852 "\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"
2853 "\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"
2854 "\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"
2855 "\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"
2856 "|\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"
2857 "\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"
2858 "\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"
2859 "\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"
2860 "\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"
2861 "\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"
2862 "\377\377\0\1\0\0\0\206\207\0\0\0\310\1\0\0\0X\214\377\377\377\0\11\0"
2863 "\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"
2864 "\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"
2865 "\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"
2866 "\377\377\0\1\0\0\0\271\210\0\0\0\377\1\0\0\0\202\203\377\377\377\0\1"
2867 "\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"
2868 "\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"
2869 "\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"
2870 "\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"
2871 "\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"
2872 "\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"
2873 "\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"
2874 "\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"
2875 "\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"
2876 "\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"
2877 "\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"
2878 "\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"
2879 "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"
2880 "\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"
2881 "\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"
2882 "\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"
2883 "\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"
2884 "\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"
2885 "\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"
2886 "\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"
2887 "\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"
2888 "\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"
2889 "\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"
2890 "\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"
2891 "\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"
2892 "\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"
2893 "\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"
2894 "\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|"
2895 "\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"
2896 "\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"
2897 "\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"
2898 "\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"
2899 "\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"
2900 "\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"
2901 "\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"
2902 "\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"
2903 "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"
2904 "\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"
2905 "\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"
2906 "\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"
2907 "\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"
2908 "\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"
2909 "\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"
2910 "\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"
2911 "\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"
2912 "\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"
2913 "\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"
2914 "\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"
2915 "\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"
2916 "\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"
2917 "\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"
2918 "\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"
2919 "\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"
2920 "\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"
2921 "\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"
2922 "\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"
2923 "\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"
2924 "\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"
2925 "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"
2926 "\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"
2927 "\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"
2928 "\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"
2929 "\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"
2930 "\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"
2931 "\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"
2932 "\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"
2933 "\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"
2934 "\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"
2935 "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"
2936 "\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"
2937 "\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"
2938 "\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"
2939 "\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"
2940 "\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"
2941 "\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"
2942 "\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"
2943 "\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377"
2944 "\377\377\377\0\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0"
2945 "\377\377\377\377\0\377\377\377\377\0\377\377\377\377\0\234\377\377\377"
2946 "\0"]
2948 known_profile_icon = [ ""
2949 "GdkP"
2950 "\0\0\5""0"
2951 "\2\1\0\2"
2952 "\0\0\0P"
2953 "\0\0\0\24"
2954 "\0\0\0\24"
2955 "\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"
2956 "\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"
2957 "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"
2958 "\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"
2959 "\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"
2960 "\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"
2961 "\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"
2962 "\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"
2963 "\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"
2964 "\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"
2965 "\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"
2966 "\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"
2967 "\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"
2968 "\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"
2969 "\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"
2970 "\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"
2971 "\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"
2972 "\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"
2973 "\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"
2974 "***;\210\210\210\356\223\223\223\377iii\377\204\204\204\377\216\216\216"
2975 "\377~~~\377zzz\377\203\203\203\377\215\215\215\377ddd\377\202\202\202"
2976 "\377xxx\356\40\40\40;\205\0\0\0\0\2&&&#\251\251\251\353\202\374\374\374"
2977 "\377\202\372\372\372\377\5\335\335\335\377\353\353\353\377\366\366\366"
2978 "\377\327\327\327\377\357\357\357\377\202\365\365\365\377\3\362\362\362"
2979 "\377\226\226\226\353\27\27\27)\204\0\0\0\0\21,,,p\354\354\354\377\355"
2980 "\355\355\377\351\351\351\377\346\346\346\377\342\342\342\377\335\335"
2981 "\335\377\334\334\334\377\330\330\330\377\324\324\324\377\320\320\320"
2982 "\377\316\316\316\377\313\313\313\377\307\307\307\377\314\314\314\377"
2983 "\35\35\35y\0\0\0\1\202\0\0\0\0\14\0\0\0\2(((\203\357\357\357\377\345"
2984 "\345\345\377\341\341\341\377\337\337\337\377\333\333\333\377\326\326"
2985 "\326\377\322\322\322\377\316\316\316\377\312\312\312\377\306\306\306"
2986 "\377\202\302\302\302\377\30\314\314\314\377\311\311\311\377\33\33\33"
2987 "\204\0\0\0\5\0\0\0\1\0\0\0\2\0\0\0\10&&&\210\356\356\356\377\342\342"
2988 "\342\377\347\347\347\377\346\346\346\377\324GG\377\337\337\337\377\324"
2989 "GG\377\333\322\322\377\324GG\377<\341@\377\324GG\377<\341@\377\321\321"
2990 "\321\377\276\276\276\377\27\27\27\214\0\0\0\15\202\0\0\0\4+\0\0\0\21"
2991 "$$$\221\355\355\355\377\345\345\345\377\344\344\344\377\340\340\340\377"
2992 "\334\334\334\377\331\331\331\377\325\325\325\377\321\321\321\377\316"
2993 "\316\316\377\312\312\312\377\306\306\306\377\307\307\307\377\313\313"
2994 "\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"
2995 "\27\"\"\"\231\354\354\354\377\346\346\346\377\342\342\342\377\337\337"
2996 "\337\377\333\333\333\377\327\327\327\377\324\324\324\377\320\320\320"
2997 "\377\314\314\314\377\310\310\310\377\305\305\305\377\301\301\301\377"
2998 "\276\276\276\377\271\271\271\377\23\23\23\235\0\0\0\35\0\0\0\10\0\0\0"
2999 "\4\0\0\0\32\40\40\40\223\310\310\310\376\202\274\274\274\377\4\272\272"
3000 "\272\377\271\271\271\377\270\270\270\377\267\267\267\377\202\271\271"
3001 "\271\377\16\270\270\270\377\266\266\266\377\265\265\265\377\264\264\264"
3002 "\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"
3003 "\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"
3004 "\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"
3005 "\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"
3006 "\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"]
3008 unknown_profile_icon = [ ""
3009 "GdkP"
3010 "\0\0\5\22"
3011 "\2\1\0\2"
3012 "\0\0\0P"
3013 "\0\0\0\24"
3014 "\0\0\0\24"
3015 "\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"
3016 "\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"
3017 "\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"
3018 "\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"
3019 "(\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"
3020 "\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"
3021 "#\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"
3022 "\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"
3023 "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"
3024 "\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"
3025 "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"
3026 "\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"
3027 "\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"
3028 "\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"
3029 "\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"
3030 "\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"
3031 "\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"
3032 "\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"
3033 "\0\0\0\0\16***\21\210\210\210G\223\223\223LiiiL\204\204\204L\34=n\300"
3034 "\14""1i\361\12""0i\374\20""4j\342CXx}dddL\202\202\202LxxxG\40\40\40\21"
3035 "\205\0\0\0\0\2&&&\13\251\251\251F\202\374\374\374L\202\372\372\372L\5"
3036 "\"Cv\310Lf\217\226@]\211\245\12""0i\377\22""7n\353\202\365\365\365L\3"
3037 "\362\362\362L\226\226\226F\27\27\27\14\204\0\0\0\0\21,,,!\354\354\354"
3038 "L\355\355\355L\351\351\351L\346\346\346L\342\342\342L\335\335\335L\334"
3039 "\334\334L\210\227\255h\12""0i\377\21""6l\352\316\316\316L\313\313\313"
3040 "L\307\307\307L\314\314\314L\35\35\35$\0\0\0\1\202\0\0\0\0\14\0\0\0\1"
3041 "((('\357\357\357L\345\345\345L\341\341\341L\337\337\337L\333\333\333"
3042 "L\326\326\326L|\215\245l\20""5l\355\12""0i\374Sj\215\205\202\302\302"
3043 "\302L\4\314\314\314L\311\311\311L\33\33\33'\0\0\0\2\202\0\0\0\1\22\0"
3044 "\0\0\2&&&(\356\356\356L\342\342\342L\347\347\347L\346\346\346L\324GG"
3045 "L\337\337\337L\22""0g\351\12""0i\377^9Z\201<\341@L\324GGL<\341@L\321"
3046 "\321\321L\276\276\276L\27\27\27)\0\0\0\4\202\0\0\0\1\22\0\0\0\5$$$+\355"
3047 "\355\355L\345\345\345L\344\344\344L\340\340\340L\334\334\334L\331\331"
3048 "\331Law\227\177`u\226\177\316\316\316L\312\312\312L\306\306\306L\307"
3049 "\307\307L\313\313\313L\272\272\272L\24\24\24,\0\0\0\7\202\0\0\0\2\27"
3050 "\0\0\0\7\"\"\"-\354\354\354L\346\346\346L\342\342\342L\337\337\337L\333"
3051 "\333\333L\327\327\327LSk\217\212Qi\216\212\314\314\314L\310\310\310L"
3052 "\305\305\305L\301\301\301L\276\276\276L\271\271\271L\23\23\23/\0\0\0"
3053 "\10\0\0\0\2\0\0\0\1\0\0\0\10\40\40\40,\310\310\310K\202\274\274\274L"
3054 "\3\272\272\272L\271\271\271L\270\270\270L\202\12""0i\377\16\271\271\271"
3055 "L\270\270\270L\266\266\266L\265\265\265L\264\264\264L\231\231\231K\16"
3056 "\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"
3057 "\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"
3058 "\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"
3059 "\4\0\0\0\4\0\0\0\2\0\0\0\1\0\0\0\0"]
3061 signal_xpm_barely = [
3062 "20 20 10 1",
3063 " c None",
3064 ". c #C6C6C6",
3065 "+ c #CCCCCC",
3066 "@ c #DBDBDB",
3067 "# c #D3D3D3",
3068 "$ c #A9B099",
3069 "% c #95A173",
3070 "& c #6B8428",
3071 "* c #B4B7AC",
3072 "= c #80924D",
3073 " .+++.",
3074 " +@@@+",
3075 " +@@@+",
3076 " +@@@+",
3077 " +@@@+",
3078 " .++++#@@@+",
3079 " +@@@@@@@@+",
3080 " +@@@@@@@@+",
3081 " +@@@@@@@@+",
3082 " +@@@@@@@@+",
3083 " $%%%%#@@@@@@@@+",
3084 " %&&&&@@@@@@@@@+",
3085 " %&&&&@@@@@@@@@+",
3086 " %&&&&@@@@@@@@@+",
3087 " %&&&&@@@@@@@@@+",
3088 "*%%%%=&&&&@@@@@@@@@+",
3089 "%&&&&&&&&&@@@@@@@@@+",
3090 "%&&&&&&&&&@@@@@@@@@+",
3091 "%&&&&&&&&&@@@@@@@@@+",
3092 "*%%%%%%%%%+++++++++."
3096 signal_xpm_best = [
3097 "20 20 6 1",
3098 " c None",
3099 ". c #9DAABF",
3100 "+ c #7B96BF",
3101 "@ c #386EBF",
3102 "# c #5982BF",
3103 "$ c #AEB4BF",
3104 " .+++.",
3105 " +@@@+",
3106 " +@@@+",
3107 " +@@@+",
3108 " +@@@+",
3109 " .++++#@@@+",
3110 " +@@@@@@@@+",
3111 " +@@@@@@@@+",
3112 " +@@@@@@@@+",
3113 " +@@@@@@@@+",
3114 " .++++#@@@@@@@@+",
3115 " +@@@@@@@@@@@@@+",
3116 " +@@@@@@@@@@@@@+",
3117 " +@@@@@@@@@@@@@+",
3118 " +@@@@@@@@@@@@@+",
3119 "$++++#@@@@@@@@@@@@@+",
3120 "+@@@@@@@@@@@@@@@@@@+",
3121 "+@@@@@@@@@@@@@@@@@@+",
3122 "+@@@@@@@@@@@@@@@@@@+",
3123 "$++++++++++++++++++."
3126 signal_xpm_none = [
3127 "20 20 6 1",
3128 " c None",
3129 ". c #C6C6C6",
3130 "+ c #CCCCCC",
3131 "@ c #DBDBDB",
3132 "# c #D3D3D3",
3133 "$ c #C2C2C2",
3134 " .+++.",
3135 " +@@@+",
3136 " +@@@+",
3137 " +@@@+",
3138 " +@@@+",
3139 " .++++#@@@+",
3140 " +@@@@@@@@+",
3141 " +@@@@@@@@+",
3142 " +@@@@@@@@+",
3143 " +@@@@@@@@+",
3144 " .++++#@@@@@@@@+",
3145 " +@@@@@@@@@@@@@+",
3146 " +@@@@@@@@@@@@@+",
3147 " +@@@@@@@@@@@@@+",
3148 " +@@@@@@@@@@@@@+",
3149 "$++++#@@@@@@@@@@@@@+",
3150 "+@@@@@@@@@@@@@@@@@@+",
3151 "+@@@@@@@@@@@@@@@@@@+",
3152 "+@@@@@@@@@@@@@@@@@@+",
3153 "$++++++++++++++++++."
3156 signal_xpm_ok = [
3157 "20 20 10 1",
3158 " c None",
3159 ". c #C6C6C6",
3160 "+ c #CCCCCC",
3161 "@ c #DBDBDB",
3162 "# c #A1A5B2",
3163 "$ c #848DA5",
3164 "% c #D3D3D3",
3165 "& c #4A5B8C",
3166 "* c #677498",
3167 "= c #B0B2B8",
3168 " .+++.",
3169 " +@@@+",
3170 " +@@@+",
3171 " +@@@+",
3172 " +@@@+",
3173 " #$$$$%@@@+",
3174 " $&&&&@@@@+",
3175 " $&&&&@@@@+",
3176 " $&&&&@@@@+",
3177 " $&&&&@@@@+",
3178 " #$$$$*&&&&@@@@+",
3179 " $&&&&&&&&&@@@@+",
3180 " $&&&&&&&&&@@@@+",
3181 " $&&&&&&&&&@@@@+",
3182 " $&&&&&&&&&@@@@+",
3183 "=$$$$*&&&&&&&&&@@@@+",
3184 "$&&&&&&&&&&&&&&@@@@+",
3185 "$&&&&&&&&&&&&&&@@@@+",
3186 "$&&&&&&&&&&&&&&@@@@+",
3187 "=$$$$$$$$$$$$$$++++."
3191 signal_xpm_low = [
3192 "20 20 8 1",
3193 " c None",
3194 ". c #C6C6C6",
3195 "+ c #CCCCCC",
3196 "@ c #DBDBDB",
3197 "# c #D3D3D3",
3198 "$ c #BFB0B5",
3199 "% c #C18799",
3200 "& c #C54F74",
3201 " .+++.",
3202 " +@@@+",
3203 " +@@@+",
3204 " +@@@+",
3205 " +@@@+",
3206 " .++++#@@@+",
3207 " +@@@@@@@@+",
3208 " +@@@@@@@@+",
3209 " +@@@@@@@@+",
3210 " +@@@@@@@@+",
3211 " .++++#@@@@@@@@+",
3212 " +@@@@@@@@@@@@@+",
3213 " +@@@@@@@@@@@@@+",
3214 " +@@@@@@@@@@@@@+",
3215 " +@@@@@@@@@@@@@+",
3216 "$%%%%#@@@@@@@@@@@@@+",
3217 "%&&&&@@@@@@@@@@@@@@+",
3218 "%&&&&@@@@@@@@@@@@@@+",
3219 "%&&&&@@@@@@@@@@@@@@+",
3220 "$%%%%++++++++++++++."
3224 ####################################################################################################
3225 # Make so we can be imported
3226 if __name__ == "__main__":
3227 if len( sys.argv ) > 1 and ( sys.argv[1] == '--version' or sys.argv[1] == '-v' ):
3228 print "WiFi-Radar version %s" % WIFI_RADAR_VERSION
3229 else:
3230 import gtk, gobject
3231 gtk.gdk.threads_init()
3232 apQueue = Queue.Queue(100)
3233 commQueue = Queue.Queue(2)
3235 logger = logging.getLogger("wrlog")
3236 logger.setLevel(confFile.get_opt_as_int('DEFAULT.loglevel'))
3237 fileLogHandler = logging.handlers.RotatingFileHandler(confFile.get_opt('DEFAULT.logfile'), maxBytes=64*1024, backupCount=5)
3238 fileLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(funcName)s: %(message)s'))
3239 logger.addHandler(fileLogHandler)
3240 if __debug__:
3241 logger.setLevel(logging.INFO)
3242 consoleLogHandler = logging.StreamHandler()
3243 consoleLogHandler.setFormatter(logging.Formatter('%(asctime)s: %(funcName)s: %(message)s'))
3244 logger.addHandler(consoleLogHandler)
3246 exit_event = threading.Event()
3247 exit_event.clear()
3248 threading.Thread(None, scanning_thread, None, (confFile, apQueue, commQueue, logger, exit_event)).start()
3249 main_radar_window = radar_window(confFile, apQueue, commQueue, logger, exit_event)
3250 gobject.timeout_add( 500, main_radar_window.update_plist_items )
3251 main_radar_window.main()