Update update_connect_buttons
[wifi-radar.git] / wifiradar / gui / g2 / __init__.py
blob7c7ef974f4a0112617eca1c3badc408c703be4cd
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # gui/g2/__init__.py - collection of classes for main UI with PyGTK
6 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
8 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
9 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
10 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
11 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
12 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
13 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
14 # Copyright (C) 2012 Anari Jalakas <anari.jalakas@gmail.com>
15 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
16 # Copyright (C) 2009-2010,2014 Sean Robinson <seankrobinson@gmail.com>
17 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
19 # This program is free software; you can redistribute it and/or modify
20 # it under the terms of the GNU General Public License as published by
21 # the Free Software Foundation; version 2 of the License.
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License in LICENSE.GPL for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 try:
35 # Py2
36 import ConfigParser as configparser
37 except ImportError:
38 # Py3
39 import configparser
41 try:
42 # Py2
43 import Queue as queue
44 except ImportError:
45 # Py3
46 import queue
48 import errno
49 import gtk
50 import logging
51 import sys
52 import threading
53 from time import sleep
55 import wifiradar.connections as connections
56 import wifiradar.misc as misc
57 from . import prefs
58 from . import profile as profile_ed
59 from . import transients
61 # create a logger
62 logger = logging.getLogger(__name__)
65 # Create a bunch of icons from files in the package.
66 known_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/known_profile.png")
67 unknown_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/unknown_profile.png")
68 signal_none_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_none.xpm")
69 signal_low_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_low.xpm")
70 signal_barely_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_barely.xpm")
71 signal_ok_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_ok.xpm")
72 signal_best_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_best.xpm")
74 def pixbuf_from_known(known):
75 """ Return a :class:`gtk.gdk.Pixbuf` icon to represent :data:`known`.
76 Any true :data:`known` value returns the icon showing previous
77 familiarity.
78 """
79 if known:
80 return known_profile_icon
81 return unknown_profile_icon
83 def pixbuf_from_signal(signal):
84 """ Return a :class:`gtk.gdk.Pixbuf` icon to indicate the :data:`signal`
85 level. :data:`signal` is as reported by iwlist (may be arbitrary
86 scale in 0-100 or -X dBm)
87 """
88 signal = int(signal)
89 # Shift signal up by 80 to convert dBm scale to arbitrary scale.
90 if signal < 0:
91 signal = signal + 80
92 # Find an icon...
93 if signal < 3:
94 return signal_none_pb
95 elif signal < 12:
96 return signal_low_pb
97 elif signal < 20:
98 return signal_barely_pb
99 elif signal < 35:
100 return signal_ok_pb
101 elif signal >= 35:
102 return signal_best_pb
105 class RadarWindow:
106 """ Center for most of the rest of the operations.
108 def __init__(self, config, apQueue, commQueue, exit_event):
109 """ Create a new RadarWindow.
111 Parameters:
113 'config' -- ConfigFile - The config file in which to store/read settings.
115 'apQueue' -- Queue - The Queue from which AP profiles are read.
117 'commQueue' -- Queue - The Queue to which to send commands to the scanning thread.
119 Returns:
121 RadarWindow instance
123 self.config = config
124 self.apQueue = apQueue
125 self.commandQueue = commQueue
126 self.access_points = {}
127 self.exit_event = exit_event
128 self.connection = None
130 self.icon = gtk.gdk.pixbuf_new_from_file("pixmaps/wifi-radar.png")
132 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL)
133 self.window.set_icon(self.icon)
134 self.window.set_border_width(10)
135 self.window.set_size_request(550, 300)
136 self.window.set_title("WiFi Radar")
137 self.window.connect('delete_event', self.delete_event)
138 self.window.connect('destroy', self.destroy)
139 # let's create all our widgets
140 self.current_network = gtk.Label()
141 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
142 self.current_network.show()
143 self.close_button = gtk.Button("Close", gtk.STOCK_CLOSE)
144 self.close_button.show()
145 self.close_button.connect('clicked', self.delete_event, None)
146 self.about_button = gtk.Button("About", gtk.STOCK_ABOUT)
147 self.about_button.show()
148 self.about_button.connect('clicked', self.show_about_info, None)
149 self.preferences_button = gtk.Button("Preferences", gtk.STOCK_PREFERENCES)
150 self.preferences_button.show()
151 self.preferences_button.connect('clicked', self.edit_preferences, None)
152 # essid + bssid known_icon known available wep_icon signal_level mode protocol channel
153 self.pstore = gtk.ListStore(str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str)
154 self.plist = gtk.TreeView(self.pstore)
155 # The icons column, known and encryption
156 self.pix_cell = gtk.CellRendererPixbuf()
157 self.wep_cell = gtk.CellRendererPixbuf()
158 self.icons_cell = gtk.CellRendererText()
159 self.icons_col = gtk.TreeViewColumn()
160 self.icons_col.pack_start(self.pix_cell, False)
161 self.icons_col.pack_start(self.wep_cell, False)
162 self.icons_col.add_attribute(self.pix_cell, 'pixbuf', 1)
163 self.icons_col.add_attribute(self.wep_cell, 'stock-id', 4)
164 self.plist.append_column(self.icons_col)
165 # The AP column
166 self.ap_cell = gtk.CellRendererText()
167 self.ap_col = gtk.TreeViewColumn("Access Point")
168 self.ap_col.pack_start(self.ap_cell, True)
169 self.ap_col.add_attribute(self.ap_cell, 'text', 0)
170 self.plist.append_column(self.ap_col)
171 # The signal column
172 self.sig_cell = gtk.CellRendererPixbuf()
173 self.signal_col = gtk.TreeViewColumn("Signal")
174 self.signal_col.pack_start(self.sig_cell, True)
175 self.signal_col.add_attribute(self.sig_cell, 'pixbuf', 5)
176 self.plist.append_column(self.signal_col)
177 # The mode column
178 self.mode_cell = gtk.CellRendererText()
179 self.mode_col = gtk.TreeViewColumn("Mode")
180 self.mode_col.pack_start(self.mode_cell, True)
181 self.mode_col.add_attribute(self.mode_cell, 'text', 6)
182 self.plist.append_column(self.mode_col)
183 # The protocol column
184 self.prot_cell = gtk.CellRendererText()
185 self.protocol_col = gtk.TreeViewColumn("802.11")
186 self.protocol_col.pack_start(self.prot_cell, True)
187 self.protocol_col.add_attribute(self.prot_cell, 'text', 7)
188 self.plist.append_column(self.protocol_col)
189 # The channel column
190 self.channel_cell = gtk.CellRendererText()
191 self.channel_col = gtk.TreeViewColumn("Channel")
192 self.channel_col.pack_start(self.channel_cell, True)
193 self.channel_col.add_attribute(self.channel_cell, 'text', 8)
194 self.plist.append_column(self.channel_col)
195 # DnD Ordering
196 self.plist.set_reorderable(True)
197 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
198 self.pstore.connect('row-deleted', self.update_auto_profile_order)
199 # enable/disable buttons based on the selected network
200 self.selected_network = self.plist.get_selection()
201 self.selected_network.connect('changed', self.on_network_selection, None)
202 # the list scroll bar
203 sb = gtk.VScrollbar(self.plist.get_vadjustment())
204 sb.show()
205 self.plist.show()
206 # Add New button
207 self.new_button = gtk.Button("_New")
208 self.new_button.connect('clicked', self.create_new_profile, misc.get_new_profile(), None)
209 self.new_button.show()
210 # Add Configure button
211 self.edit_button = gtk.Button("C_onfigure")
212 self.edit_button.connect('clicked', self.edit_profile, None)
213 self.edit_button.show()
214 self.edit_button.set_sensitive(False)
215 # Add Delete button
216 self.delete_button = gtk.Button("_Delete")
217 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
218 self.delete_button.show()
219 self.delete_button.set_sensitive(False)
220 # Add Connect button
221 self.connect_button = gtk.Button("Co_nnect")
222 self.connect_button.connect('clicked', self.connect_profile, None)
223 # Add Disconnect button
224 self.disconnect_button = gtk.Button("D_isconnect")
225 self.disconnect_button.connect('clicked', self.disconnect_profile, None)
226 # lets add our widgets
227 rows = gtk.VBox(False, 3)
228 net_list = gtk.HBox(False, 0)
229 listcols = gtk.HBox(False, 0)
230 prows = gtk.VBox(False, 0)
231 # lets start packing
232 # the network list
233 net_list.pack_start(self.plist, True, True, 0)
234 net_list.pack_start(sb, False, False, 0)
235 # the rows level
236 rows.pack_start(net_list , True, True, 0)
237 rows.pack_start(self.current_network, False, True, 0)
238 # the list columns
239 listcols.pack_start(rows, True, True, 0)
240 listcols.pack_start(prows, False, False, 5)
241 # the list buttons
242 prows.pack_start(self.new_button, False, False, 2)
243 prows.pack_start(self.edit_button, False, False, 2)
244 prows.pack_start(self.delete_button, False, False, 2)
245 prows.pack_end(self.connect_button, False, False, 2)
246 prows.pack_end(self.disconnect_button, False, False, 2)
248 self.window.action_area.pack_start(self.about_button)
249 self.window.action_area.pack_start(self.preferences_button)
250 self.window.action_area.pack_start(self.close_button)
252 rows.show()
253 prows.show()
254 listcols.show()
255 self.window.vbox.add(listcols)
256 self.window.vbox.set_spacing(3)
257 self.window.show_all()
259 # Now, immediately hide these two. The proper one will be
260 # displayed later, based on interface state. -BEF-
261 self.disconnect_button.hide()
262 self.connect_button.hide()
263 self.connect_button.set_sensitive(False)
265 # set up connection manager for later use
266 self.connection = connections.ConnectionManager(self.commandQueue)
267 self.connection.set_config(self.config)
268 # set up status window for later use
269 self.status_window = transients.StatusWindow(self)
270 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
272 # Add our known profiles in order
273 for profile_name in self.config.auto_profile_order:
274 profile_name = profile_name.strip()
275 self.access_points[profile_name] = self.config.get_profile(profile_name)
276 wep = None
277 if self.access_points[profile_name]['encrypted']:
278 wep = gtk.STOCK_DIALOG_AUTHENTICATION
279 if self.access_points[profile_name]['roaming']:
280 ap_name = self.access_points[profile_name]['essid'] + "\n" + ' Multiple APs'
281 else:
282 ap_name = self.access_points[profile_name]['essid'] + "\n" + self.access_points[profile_name]['bssid']
283 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'] ])
284 # This is the first run (or, at least, no config file was present), so pop up the preferences window
285 try:
286 new_file = self.config.get_opt_as_bool('DEFAULT', 'new_file')
287 if (new == True):
288 self.config.remove_option('DEFAULT', 'new_file')
289 self.edit_preferences(self.preferences_button)
290 except configparser.NoOptionError:
291 pass
293 def main(self):
294 """ Begin running RadarWindow in Gtk event loop.
296 Parameters:
298 nothing
300 Returns:
302 nothing
304 gtk.main()
306 def destroy(self, widget=None):
307 """ Quit application.
309 Parameters:
311 'widget' -- gtk.Widget - The widget sending the event.
313 Returns:
315 nothing
317 if self.status_window:
318 self.status_window.destroy()
319 gtk.main_quit()
321 def delete_event(self, widget, data=None):
322 """ Kill scanning thread, update profile order for config file, and ask to be destroyed.
324 Parameters:
326 'widget' -- gtk.Widget - The widget sending the event.
328 'data' -- tuple - list of arbitrary arguments (not used)
330 Returns:
332 boolean -- always return False (i.e. do not propigate the signal which called)
334 self.window.hide()
335 # process GTK events so that window hides more quickly
336 if sys.modules.has_key("gtk"):
337 while gtk.events_pending():
338 gtk.main_iteration(False)
339 # Let other threads know it is time to exit
340 self.exit_event.set()
341 # Wait for all other threads to exit before continuing
342 while threading.activeCount() > 1:
343 sleep(0.25)
344 self.destroy()
345 return False
347 def update_network_info(self, profile=None, ip=None):
348 """ Update the current ip and essid shown to the user.
350 if (profile is None) and (ip is None):
351 self.current_network.set_text("Not Connected.")
352 else:
353 self.current_network.set_text('Connected to {}\nIP Address {}'.format(profile, ip))
355 def update_connect_buttons(self, connected=False):
356 """ Set the state of connect/disconnect buttons to reflect the
357 current connected state.
359 if connected:
360 self.connect_button.hide()
361 self.disconnect_button.show()
362 else:
363 self.disconnect_button.hide()
364 self.connect_button.show()
366 def update_plist_items(self, ap):
367 """ Updates the on-screen profiles list.
369 Parameters:
371 'ap' -- dictionary -- The AP found by scanning_thread.
373 Returns:
375 nothing
377 # Check for roaming profile.
378 ap_name = misc.make_section_name(ap['essid'], '')
379 profile = self.config.get_profile(ap_name)
380 prow_iter = self.get_row_by_ap(ap['essid'], ' Multiple APs')
381 ap_display = ap['essid'] + "\n" + ' Multiple APs'
382 if not profile:
383 # Check for normal profile.
384 ap_name = misc.make_section_name(ap['essid'], ap['bssid'])
385 profile = self.config.get_profile(ap_name)
386 prow_iter = self.get_row_by_ap(ap['essid'], ap['bssid'])
387 ap_display = ap['essid'] + "\n" + ap['bssid']
388 wep = None
389 if prow_iter is not None:
390 # the AP is in the list of APs on the screen
391 # Set the 'known' values; False is default, overridden to True by self.access_points
392 ap['known'] = self.access_points[ap_name]['known']
393 self.pstore.set_value(prow_iter, 1, self.pixbuf_from_known(ap['known']))
394 self.pstore.set_value(prow_iter, 2, ap['known'])
395 self.pstore.set_value(prow_iter, 3, ap['available'])
396 if ap['encrypted']:
397 wep = gtk.STOCK_DIALOG_AUTHENTICATION
398 self.pstore.set_value(prow_iter, 4, wep)
399 self.pstore.set_value(prow_iter, 5, self.pixbuf_from_signal(self.access_points[ap_name]['signal']))
400 self.pstore.set_value(prow_iter, 6, ap['mode'])
401 self.pstore.set_value(prow_iter, 7, ap['protocol'])
402 self.pstore.set_value(prow_iter, 8, self.access_points[ap_name]['channel'])
403 else:
404 # the AP is not in the list of APs on the screen
405 self.pstore.append([ap_display, self.pixbuf_from_known(ap['known']), ap['known'], ap['available'], wep, self.pixbuf_from_signal(ap['signal']), ap['mode'], ap['protocol'], ap['channel']])
406 #print "update_plist_items: new ap", ap[ 'essid' ], ap['bssid']
408 def update_ap_list(self, ap):
409 """ Updates the record-keeping profiles list.
411 Parameters:
413 'ap' -- dictionary -- The AP found by scanning_thread.
415 Returns:
417 nothing
419 # Check for roaming profile.
420 ap_name = misc.make_section_name(ap['essid'], '')
421 profile = self.config.get_profile(ap_name)
422 if not profile:
423 # Check for normal profile.
424 ap_name = misc.make_section_name(ap['essid'], ap['bssid'])
425 profile = self.config.get_profile(ap_name)
426 if not profile:
427 # No profile, so make a new one.
428 profile = misc.get_new_profile()
429 if self.access_points.has_key(ap_name):
430 # This AP has been configured and should be updated
431 self.access_points[ap_name]['known'] = profile['known']
432 self.access_points[ap_name]['available'] = ap['available']
433 self.access_points[ap_name]['encrypted'] = ap['encrypted']
434 self.access_points[ap_name]['mode'] = ap['mode']
435 self.access_points[ap_name]['protocol'] = ap['protocol']
436 if self.access_points[ap_name]['roaming']:
437 # Roaming
438 if self.access_points[ap_name]['bssid'] == ap['bssid']:
439 # Same AP for this roaming profile
440 self.access_points[ap_name]['signal'] = ap['signal']
441 else:
442 # Different AP
443 if int(self.access_points[ap_name]['signal']) < int(ap['signal']):
444 # Stronger signal with this AP, so promote it to preferred
445 self.access_points[ap_name]['signal'] = ap['signal']
446 self.access_points[ap_name]['channel'] = ap['channel']
447 self.access_points[ap_name]['bssid'] = ap['bssid']
448 else:
449 # Not roaming
450 self.access_points[ap_name]['signal'] = ap['signal']
451 self.access_points[ap_name]['channel'] = ap['channel']
452 else:
453 # Not seen before, begin tracking it.
454 self.access_points[ap_name] = profile
456 def update_window(self):
457 """ Updates the main user interface.
459 Parameters:
461 nothing
463 Returns:
465 boolean -- always return True
467 with gtk.gdk.lock:
468 self.update_network_info()
469 self.update_connect_buttons()
470 while True:
471 # Get APs scanned by iwlist
472 try:
473 ap = self.apQueue.get_nowait()
474 except queue.Empty:
475 break
476 else:
477 self.update_ap_list(ap)
478 self.update_plist_items(ap)
479 return True
491 def get_row_by_ap(self, essid, bssid):
492 """ Return row which holds specified ESSID and BSSID.
494 Parameters:
496 'essid' -- string - ESSID to match
498 'bssid' -- string - BSSID to match
500 Returns:
502 gtk.TreeIter or None -- pointer to the row containing the match or None for no match found
504 if bssid == "roaming":
505 for row in self.pstore:
506 if ( row[0][:row[0].find("\n")] == essid ):
507 #print "roaming match:", row.iter, essid, bssid
508 return row.iter
509 else:
510 for row in self.pstore:
511 if ( row[0] == essid + "\n" + bssid ):
512 #print "normal match:", row.iter, essid, bssid
513 return row.iter
514 return None
516 def on_network_selection(self, widget, data=None):
517 """ Enable/disable buttons based on the selected network.
519 Parameters:
521 'widget' -- gtk.Widget - The widget sending the event.
523 'data' -- tuple - list of arbitrary arguments (not used)
525 Returns:
527 nothing
529 (store, selected_iter) = self.selected_network.get_selected()
530 #print self.access_points[(misc.make_section_name(store.get_value(selected_iter, 0), store.get_value(selected_iter, 1) ))]
531 # if no networks are selected, disable all buttons except New
532 # (this occurs after a drag-and-drop)
533 if selected_iter is None:
534 self.edit_button.set_sensitive(False)
535 self.delete_button.set_sensitive(False)
536 self.connect_button.set_sensitive(False)
537 return
538 # enable/disable buttons
539 self.connect_button.set_sensitive(True)
540 if ( store.get_value(selected_iter, 2) == True ): # is selected network known?
541 self.edit_button.set_sensitive(True)
542 self.delete_button.set_sensitive(True)
543 else:
544 self.edit_button.set_sensitive(True)
545 self.delete_button.set_sensitive(False)
547 def show_about_info(self, widget, data=None):
548 """ Init and run the about dialog
550 Parameters:
552 'widget' -- gtk.Widget - The widget sending the event.
554 'data' -- tuple - list of arbitrary arguments (not used)
556 Returns:
558 nothing
560 about = transients.AboutDialog()
561 about.run()
562 about.destroy()
564 def edit_preferences(self, widget, data=None):
565 """ Init and run the preferences dialog
567 Parameters:
569 'widget' -- gtk.Widget - The widget sending the event.
571 'data' -- tuple - list of arbitrary arguments (not used)
573 Returns:
575 nothing
577 # get raw strings from config file
578 self.config.raw = True
579 prefs_editor = prefs.PreferencesEditor(self, self.config)
580 response = prefs_editor.run()
581 if response == int(gtk.RESPONSE_ACCEPT):
582 prefs_editor.save()
583 prefs_editor.destroy()
584 # get cooked strings from config file
585 self.config.raw = False
587 def create_new_profile(self, widget, profile, data=None):
588 """ Respond to a request to create a new AP profile
590 Parameters:
592 'widget' -- gtk.Widget - The widget sending the event.
594 'profile' -- dictionary - The AP profile to use as basis for new profile.
596 'data' -- tuple - list of arbitrary arguments (not used)
598 Returns:
600 boolean -- True if a profile was created and False if profile creation was canceled.
602 profile_editor = profile_ed.ProfileEditor(self, profile)
603 try:
604 profile = profile_editor.run()
605 except ValueError:
606 error_dlg = transients.ErrorDialog(profile_editor.dialog, "Cannot save empty ESSID")
607 del error_dlg
608 return False
609 finally:
610 profile_editor.destroy()
611 if profile:
612 (store, selected_iter) = self.plist.get_selection().get_selected()
613 if selected_iter is not None:
614 store.remove(selected_iter)
615 if profile['roaming']:
616 apname = misc.make_section_name(profile['essid'], '')
617 else:
618 apname = misc.make_section_name(profile['essid'], profile['bssid'])
619 # Check that the ap does not exist already
620 if apname in self.config.profiles():
621 error_dlg = transients.ErrorDialog(self.window, "A profile for %s already exists" % (apname))
622 del error_dlg
623 # try again
624 self.access_points[ apname ] = profile
625 self.config.set_section(apname, profile)
626 # if it is not in the auto_profile_order add it
627 if apname not in self.config.auto_profile_order:
628 self.config.auto_profile_order.insert(0, apname)
629 # add to the store
630 wep = None
631 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
632 try:
633 self.config.write()
634 except IOError as e:
635 if e.errno == errno.ENOENT:
636 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
637 del error_dlg
638 else:
639 raise e
640 # Add AP to the list displayed to user
641 if profile['roaming']:
642 ap_name = profile['essid'] + "\n" + ' Multiple APs'
643 while True:
644 prow_iter = self.get_row_by_ap(profile['essid'], 'roaming')
645 if prow_iter:
646 self.pstore.remove(prow_iter)
647 else:
648 break
649 else:
650 ap_name = profile['essid'] + "\n" + profile['bssid']
651 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']])
652 return True
653 else:
654 # Did not create new profile
655 return False
657 def edit_profile(self, widget, data=None):
658 """ Respond to a request to edit an AP profile. Edit selected AP profile if it
659 is known. Otherwise, create a new profile with the selected ESSID and BSSID.
661 Parameters:
663 'widget' -- gtk.Widget - The widget sending the event.
665 'data' -- tuple - list of arbitrary arguments (not used)
667 Returns:
669 nothing
671 (store, selected_iter) = self.plist.get_selection().get_selected()
672 if not selected_iter:
673 # No AP is selected
674 return
675 (essid, bssid) = str(self.pstore.get_value(selected_iter, 0)).split("\n")
676 if bssid == ' Multiple APs':
677 # AP list says this is a roaming profile
678 apname = misc.make_section_name(essid, '')
679 else:
680 # AP list says this is NOT a roaming profile
681 apname = misc.make_section_name(essid, bssid)
682 profile = self.config.get_profile(apname)
683 if profile:
684 # A profile was found in the config file
685 profile['bssid'] = self.access_points[apname]['bssid']
686 profile_editor = profile_ed.ProfileEditor(self, profile)
687 try:
688 # try editing the profile
689 edited_profile = profile_editor.run()
690 except ValueError:
691 error_dlg = transients.ErrorDialog(profile_editor.dialog, "Cannot save empty ESSID")
692 del error_dlg
693 return False
694 finally:
695 # Always remove profile editor window from screen
696 profile_editor.destroy()
697 if edited_profile:
698 # A profile was returned by the editor
699 old_index = None
700 row = None
701 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
702 # ESSID, BSSID, or roaming was changed in profile editor
703 try:
704 self.commandQueue.put('pause')
705 self.commandQueue.join()
706 except queue.Full:
707 pass
708 if profile['roaming']:
709 # The old profile was a roaming profile
710 old_ap = misc.make_section_name(profile['essid'], '')
711 else:
712 # The old profile was NOT a roaming profile
713 old_ap = misc.make_section_name(profile['essid'], profile['bssid'])
714 # Find where old profile was in auto order
715 old_index = self.config.auto_profile_order.index(old_ap)
716 # Remove old profile and get its place in AP list
717 row = self.delete_profile(selected_iter, old_ap)
718 self.config.remove_section(old_ap)
719 try:
720 # Add AP to the list displayed to user
721 self.apQueue.put_nowait(edited_profile)
722 self.commandQueue.put('scan')
723 except queue.Full:
724 pass
725 if edited_profile['roaming']:
726 # New profile is a roaming profile
727 apname = misc.make_section_name(edited_profile['essid'], '')
728 ap_display = edited_profile['essid'] + "\n" + ' Multiple APs'
729 # Remove all other profiles that match the new profile ESSID
730 while True:
731 prow_iter = self.get_row_by_ap(edited_profile['essid'], 'roaming')
732 if prow_iter:
733 self.pstore.remove(prow_iter)
734 else:
735 break
736 else:
737 # New profile is NOT a roaming profile
738 apname = misc.make_section_name(edited_profile['essid'], edited_profile['bssid'])
739 ap_display = edited_profile['essid'] + "\n" + edited_profile['bssid']
740 # Insert the new profile in the same position as the one being replaced
741 if old_index is not None:
742 # Old profile was in auto order list
743 self.config.auto_profile_order.insert(old_index, apname)
744 if (( row is not None) and (self.pstore.iter_is_valid(row)) ):
745 self.pstore.insert_before(row, [ap_display, None, None, None, None, None, None, None, None])
746 self.access_points[apname] = edited_profile
747 self.config.set_section(apname, edited_profile)
748 try:
749 # Save updated profile to config file
750 self.config.write()
751 except IOError as e:
752 if e.errno == errno.ENOENT:
753 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
754 del error_dlg
755 else:
756 raise e
757 else:
758 # The AP does not already have a profile
759 profile = misc.get_new_profile()
760 (profile['essid'], profile['bssid']) = store.get_value(selected_iter, 0).split("\n")
761 self.create_new_profile(widget, profile, data)
763 def delete_profile(self, selected_iter, apname):
764 """ Delete an AP profile (i.e. make profile unknown)
766 Parameters:
768 'selected_iter' -- gtk.TreeIter - The selected row.
770 'apname' -- string - The configuration file section to remove
772 Returns:
774 gtk.TreeIter -- the iter for the row removed from the gtk.ListStore
776 # Remove it
777 del self.access_points[apname]
778 self.config.remove_section(apname)
779 logger.info(apname)
780 if apname in self.config.auto_profile_order:
781 self.config.auto_profile_order.remove(apname)
782 self.pstore.remove(selected_iter)
783 # Let's save our current state
784 self.update_auto_profile_order()
785 try:
786 self.config.write()
787 except IOError as e:
788 if e.errno == errno.ENOENT:
789 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
790 del error_dlg
791 else:
792 raise e
793 return selected_iter
795 def delete_profile_with_check(self, widget, data=None):
796 """ Respond to a request to delete an AP profile (i.e. make profile unknown)
797 Check with user first.
799 Parameters:
801 'widget' -- gtk.Widget - The widget sending the event.
803 'data' -- tuple - list of arbitrary arguments (not used)
805 Returns:
807 nothing
809 (store, selected_iter) = self.plist.get_selection().get_selected()
810 if not selected_iter: return
811 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
812 if bssid == ' Multiple APs':
813 apname = misc.make_section_name(essid, '')
814 else:
815 apname = misc.make_section_name(essid, bssid)
816 profile = self.config.get_profile(apname)
817 if profile['roaming']:
818 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, ))
819 else:
820 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))
821 known = store.get_value(selected_iter, 1)
822 if not known: return
823 res = dlg.run()
824 dlg.destroy()
825 del dlg
826 if res == gtk.RESPONSE_NO:
827 return
828 self.delete_profile(selected_iter, apname)
830 def connect_profile(self, widget, profile, data=None):
831 """ Respond to a request to connect to an AP.
833 Parameters:
835 'widget' -- gtk.Widget - The widget sending the event.
837 'profile' -- dictionary - The AP profile to which to connect.
839 'data' -- tuple - list of arbitrary arguments (not used)
841 Returns:
843 nothing
845 (store, selected_iter) = self.plist.get_selection().get_selected()
846 if not selected_iter: return
847 (essid, bssid) = store.get_value(selected_iter, 0).split("\n")
848 known = store.get_value(selected_iter, 2)
849 if not known:
850 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?")
851 res = dlg.run()
852 dlg.destroy()
853 del dlg
854 if res == gtk.RESPONSE_NO:
855 return
856 profile = misc.get_new_profile()
857 profile['essid'] = essid
858 profile['bssid'] = bssid
859 if not self.create_new_profile(widget, profile, data):
860 return
861 else:
862 # Check for roaming profile.
863 ap_name = misc.make_section_name(essid, '')
864 profile = self.config.get_profile(ap_name)
865 if not profile:
866 # Check for normal profile.
867 ap_name = misc.make_section_name(essid, bssid)
868 profile = self.config.get_profile(ap_name)
869 if not profile:
870 # No configured profile
871 return
872 profile['bssid'] = self.access_points[ap_name]['bssid']
873 profile['channel'] = self.access_points[ap_name]['channel']
874 self.connection.connect_to_network(profile, self.status_window)
876 def disconnect_profile(self, widget, data=None):
877 """ Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
879 Parameters:
881 'widget' -- gtk.Widget - The widget sending the event.
883 'data' -- tuple - list of arbitrary arguments (not used)
885 Returns:
887 nothing
889 if data == "cancel":
890 self.status_window.update_message("Canceling connection...")
891 if sys.modules.has_key("gtk"):
892 while gtk.events_pending():
893 gtk.main_iteration(False)
894 sleep(1)
895 self.connection.disconnect_interface()
897 def profile_order_updater(self, model, path, iter, auto_profile_order):
900 if model.get_value(iter, 2) is True:
901 (essid, bssid) = model.get_value(iter, 0).split("\n")
902 if bssid == ' Multiple APs':
903 bssid = ''
904 apname = misc.make_section_name(essid, bssid)
905 auto_profile_order.append(apname)
907 def update_auto_profile_order(self, widget=None, data=None, data2=None):
908 """ Update the config file auto profile order from the on-screen order
910 Parameters:
912 'widget' -- gtk.Widget - The widget sending the event.
914 'data' -- tuple - list of arbitrary arguments (not used)
916 'data2' -- tuple - list of arbitrary arguments (not used)
918 Returns:
920 nothing
922 # recreate the auto_profile_order
923 auto_profile_order = []
924 self.pstore.foreach(self.profile_order_updater, auto_profile_order)
925 self.config.auto_profile_order = auto_profile_order
926 try:
927 self.config.write()
928 except IOError as e:
929 if e.errno == errno.ENOENT:
930 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
931 del error_dlg
932 else:
933 raise e
936 # Make so we can be imported
937 if __name__ == "__main__":
938 pass