Assign the ConfigManager directly
[wifi-radar.git] / wifiradar / gui / g2 / __init__.py
blobf447b041920bd3576130295eb191ccdd5b0b2751
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 import errno
35 import logging
36 import sys
38 import glib
39 import gtk
41 import wifiradar.connections as connections
42 import wifiradar.misc as misc
43 from wifiradar.pubsub import Message
44 from . import prefs
45 from . import profile as profile_ed
46 from . import transients
48 # create a logger
49 logger = logging.getLogger(__name__)
52 # Create a bunch of icons from files in the package.
53 known_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/known_profile.png")
54 unknown_profile_icon = gtk.gdk.pixbuf_new_from_file("pixmaps/unknown_profile.png")
55 signal_none_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_none.xpm")
56 signal_low_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_low.xpm")
57 signal_barely_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_barely.xpm")
58 signal_ok_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_ok.xpm")
59 signal_best_pb = gtk.gdk.pixbuf_new_from_file("pixmaps/signal_best.xpm")
61 def pixbuf_from_known(known):
62 """ Return a :class:`gtk.gdk.Pixbuf` icon to represent :data:`known`.
63 Any true :data:`known` value returns the icon showing previous
64 familiarity.
65 """
66 if known:
67 return known_profile_icon
68 return unknown_profile_icon
70 def pixbuf_from_signal(signal):
71 """ Return a :class:`gtk.gdk.Pixbuf` icon to indicate the :data:`signal`
72 level. :data:`signal` is as reported by iwlist (may be arbitrary
73 scale in 0-100 or -X dBm)
74 """
75 signal = int(signal)
76 # Shift signal up by 80 to convert dBm scale to arbitrary scale.
77 if signal < 0:
78 signal = signal + 80
79 # Find an icon...
80 if signal < 3:
81 return signal_none_pb
82 elif signal < 12:
83 return signal_low_pb
84 elif signal < 20:
85 return signal_barely_pb
86 elif signal < 35:
87 return signal_ok_pb
88 elif signal >= 35:
89 return signal_best_pb
92 class RadarWindow:
93 def __init__(self, msg_pipe):
94 """ Create a new RadarWindow wanting to communicate through
95 :data:`msg_pipe`, a :class:`multiprocessing.Connection`.
96 """
97 self.access_points = {}
98 self.connection = None
99 self.msg_pipe = msg_pipe
101 gtk.gdk.threads_init()
103 self.icon = gtk.gdk.pixbuf_new_from_file("pixmaps/wifi-radar.png")
105 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL)
106 self.window.set_icon(self.icon)
107 self.window.set_border_width(10)
108 self.window.set_size_request(550, 300)
109 self.window.set_title("WiFi Radar")
110 self.window.connect('delete_event', self.delete_event)
111 self.window.connect('destroy', self.destroy)
112 # let's create all our widgets
113 self.current_network = gtk.Label()
114 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
115 self.current_network.show()
116 self.close_button = gtk.Button("Close", gtk.STOCK_CLOSE)
117 self.close_button.show()
118 self.close_button.connect('clicked', self.delete_event, None)
119 self.about_button = gtk.Button("About", gtk.STOCK_ABOUT)
120 self.about_button.show()
121 self.about_button.connect('clicked', self.show_about_info, None)
122 self.preferences_button = gtk.Button("Preferences", gtk.STOCK_PREFERENCES)
123 self.preferences_button.show()
124 self.preferences_button.connect('clicked', self.edit_preferences, None)
125 # essid bssid known_icon known available wep_icon signal_level mode protocol channel
126 self.pstore = gtk.ListStore(str, str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str)
127 self.plist = gtk.TreeView(self.pstore)
128 # The icons column, known and encryption
129 self.pix_cell = gtk.CellRendererPixbuf()
130 self.wep_cell = gtk.CellRendererPixbuf()
131 self.icons_cell = gtk.CellRendererText()
132 self.icons_col = gtk.TreeViewColumn()
133 self.icons_col.pack_start(self.pix_cell, False)
134 self.icons_col.pack_start(self.wep_cell, False)
135 self.icons_col.add_attribute(self.pix_cell, 'pixbuf', 2)
136 self.icons_col.add_attribute(self.wep_cell, 'stock-id', 5)
137 self.plist.append_column(self.icons_col)
138 # The AP column
139 self.ap_cell = gtk.CellRendererText()
140 self.ap_col = gtk.TreeViewColumn("Access Point")
141 self.ap_col.pack_start(self.ap_cell, True)
142 self.ap_col.set_cell_data_func(self.ap_cell, self._set_ap_col_value)
143 self.plist.append_column(self.ap_col)
144 # The signal column
145 self.sig_cell = gtk.CellRendererPixbuf()
146 self.signal_col = gtk.TreeViewColumn("Signal")
147 self.signal_col.pack_start(self.sig_cell, True)
148 self.signal_col.add_attribute(self.sig_cell, 'pixbuf', 6)
149 self.plist.append_column(self.signal_col)
150 # The mode column
151 self.mode_cell = gtk.CellRendererText()
152 self.mode_col = gtk.TreeViewColumn("Mode")
153 self.mode_col.pack_start(self.mode_cell, True)
154 self.mode_col.add_attribute(self.mode_cell, 'text', 7)
155 self.plist.append_column(self.mode_col)
156 # The protocol column
157 self.prot_cell = gtk.CellRendererText()
158 self.protocol_col = gtk.TreeViewColumn("802.11")
159 self.protocol_col.pack_start(self.prot_cell, True)
160 self.protocol_col.add_attribute(self.prot_cell, 'text', 8)
161 self.plist.append_column(self.protocol_col)
162 # The channel column
163 self.channel_cell = gtk.CellRendererText()
164 self.channel_col = gtk.TreeViewColumn("Channel")
165 self.channel_col.pack_start(self.channel_cell, True)
166 self.channel_col.add_attribute(self.channel_cell, 'text', 9)
167 self.plist.append_column(self.channel_col)
168 # DnD Ordering
169 self.plist.set_reorderable(True)
170 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
171 self.pstore.connect('row-deleted', self.update_auto_profile_order)
172 # enable/disable buttons based on the selected network
173 self.selected_network = self.plist.get_selection()
174 self.selected_network.connect('changed', self.on_network_selection, None)
175 # the list scroll bar
176 sb = gtk.VScrollbar(self.plist.get_vadjustment())
177 sb.show()
178 self.plist.show()
179 # Add New button
180 self.new_button = gtk.Button("_New")
181 self.new_button.connect('clicked', self.create_new_profile, misc.get_new_profile(), None)
182 self.new_button.show()
183 # Add Configure button
184 self.edit_button = gtk.Button("C_onfigure")
185 self.edit_button.connect('clicked', self.edit_profile, None)
186 self.edit_button.show()
187 self.edit_button.set_sensitive(False)
188 # Add Delete button
189 self.delete_button = gtk.Button("_Delete")
190 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
191 self.delete_button.show()
192 self.delete_button.set_sensitive(False)
193 # Add Connect button
194 self.connect_button = gtk.Button("Co_nnect")
195 self.connect_button.connect('clicked', self.connect_profile, None)
196 # Add Disconnect button
197 self.disconnect_button = gtk.Button("D_isconnect")
198 self.disconnect_button.connect('clicked', self.disconnect_profile, None)
199 # lets add our widgets
200 rows = gtk.VBox(False, 3)
201 net_list = gtk.HBox(False, 0)
202 listcols = gtk.HBox(False, 0)
203 prows = gtk.VBox(False, 0)
204 # lets start packing
205 # the network list
206 net_list.pack_start(self.plist, True, True, 0)
207 net_list.pack_start(sb, False, False, 0)
208 # the rows level
209 rows.pack_start(net_list , True, True, 0)
210 rows.pack_start(self.current_network, False, True, 0)
211 # the list columns
212 listcols.pack_start(rows, True, True, 0)
213 listcols.pack_start(prows, False, False, 5)
214 # the list buttons
215 prows.pack_start(self.new_button, False, False, 2)
216 prows.pack_start(self.edit_button, False, False, 2)
217 prows.pack_start(self.delete_button, False, False, 2)
218 prows.pack_end(self.connect_button, False, False, 2)
219 prows.pack_end(self.disconnect_button, False, False, 2)
221 self.window.action_area.pack_start(self.about_button)
222 self.window.action_area.pack_start(self.preferences_button)
223 self.window.action_area.pack_start(self.close_button)
225 rows.show()
226 prows.show()
227 listcols.show()
228 self.window.vbox.add(listcols)
229 self.window.vbox.set_spacing(3)
230 self.window.show_all()
232 # Now, immediately hide these two. The proper one will be
233 # displayed later, based on interface state. -BEF-
234 self.disconnect_button.hide()
235 self.connect_button.hide()
236 self.connect_button.set_sensitive(False)
238 # set up connection manager for later use
239 self.connection = connections.ConnectionManager(self.commandQueue)
240 self.connection.set_config(self.config)
241 # set up status window for later use
242 self.status_window = transients.StatusWindow(self)
243 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
245 self._running = True
246 # Check for incoming messages every 25 ms, a.k.a. 40 Hz.
247 glib.timeout_add(25, self.run)
248 # This is the first run (or, at least, no config file was present), so pop up the preferences window
249 try:
250 new_file = self.config.get_opt_as_bool('DEFAULT', 'new_file')
251 if (new == True):
252 self.config.remove_option('DEFAULT', 'new_file')
253 self.edit_preferences(self.preferences_button)
254 except configparser.NoOptionError:
255 pass
257 gtk.main()
259 def run(self):
260 """ Watch for incoming messages.
262 if self.msg_pipe.poll():
263 try:
264 msg = self.msg_pipe.recv()
265 except (EOFError, IOError) as e:
266 # This is bad, really bad.
267 logger.critical('read on closed ' +
268 'Pipe ({}), failing...'.format(rfd))
269 raise misc.PipeError(e)
270 else:
271 self._check_message(msg)
272 return self._running
274 def _check_message(self, msg):
275 """ Process incoming messages.
277 if msg.topic == 'EXIT':
278 self.delete_event()
279 elif msg.topic == 'CONFIG-UPDATE':
280 # Replace configuration manager with the one in msg.details.
281 self.config = msg.details
282 elif msg.topic == 'PROFILE-UPDATE':
283 self.update_plist_items(msg.details)
284 else:
285 logger.warning('unrecognized Message: "{}"'.format(msg))
287 def destroy(self, widget=None):
288 """ Quit the Gtk event loop. :data:`widget` is the widget
289 sending the signal, but it is ignored.
291 if self.status_window:
292 self.status_window.destroy()
293 gtk.main_quit()
295 def delete_event(self, widget=None, data=None):
296 """ Shutdown the application. :data:`widget` is the widget sending
297 the signal and :data:`data` is a list of arbitrary arguments,
298 both are ignored. Always returns False to not propigate the
299 signal which called :func:`delete_event`.
301 self._running = False
302 self.msg_pipe.send(Message('EXIT', ''))
303 self.msg_pipe.close()
304 self.window.hide()
305 # process GTK events so that window hides more quickly
306 if sys.modules.has_key("gtk"):
307 while gtk.events_pending():
308 gtk.main_iteration(False)
309 self.destroy()
310 return False
312 def update_network_info(self, profile=None, ip=None):
313 """ Update the current ip and essid shown to the user.
315 if (profile is None) and (ip is None):
316 self.current_network.set_text("Not Connected.")
317 else:
318 self.current_network.set_text('Connected to {}\nIP Address {}'.format(profile, ip))
320 def update_connect_buttons(self, connected=False):
321 """ Set the state of connect/disconnect buttons to reflect the
322 current connected state.
324 if connected:
325 self.connect_button.hide()
326 self.disconnect_button.show()
327 else:
328 self.disconnect_button.hide()
329 self.connect_button.show()
331 def update_plist_items(self, profile):
332 """ Updates the display of :data:`profile`.
334 if profile['roaming']:
335 prow_iter = self.get_row_by_ap(profile['essid'])
336 else:
337 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
339 if prow_iter is None:
340 # the AP is not in the list of APs on the screen
341 self.add_profile(profile)
342 else:
343 # the AP is in the list of APs on the screen
344 wep = None
345 if profile['encrypted']:
346 wep = gtk.STOCK_DIALOG_AUTHENTICATION
347 # Update the Gtk objects.
348 self.pstore.set_value(prow_iter, 2, pixbuf_from_known(profile['known']))
349 self.pstore.set_value(prow_iter, 3, profile['known'])
350 self.pstore.set_value(prow_iter, 4, profile['available'])
351 self.pstore.set_value(prow_iter, 5, wep)
352 self.pstore.set_value(prow_iter, 6, pixbuf_from_signal(profile['signal']))
353 self.pstore.set_value(prow_iter, 7, profile['mode'])
354 self.pstore.set_value(prow_iter, 8, profile['protocol'])
355 self.pstore.set_value(prow_iter, 9, profile['channel'])
357 def _set_ap_col_value(self, column, cell, model, iter):
358 """ Set the text attribute of :data:`column` to the first two
359 :data:`model` values joined by a newline. This is for
360 displaying the :data:`essid` and :data:`bssid` in a single
361 cell column.
363 essid = model.get_value(iter, 0)
364 bssid = model.get_value(iter, 1)
365 cell.set_property('text', '\n'.join([essid, bssid]))
367 def get_row_by_ap(self, essid, bssid=' Multiple APs'):
368 """ Returns a :class:`gtk.TreeIter` for the row which holds
369 :data:`essid` and :data:`bssid`.
371 :data:`bssid` is optional. If not given, :func:`get_row_by_ap`
372 will try to match a roaming profile with the given :data:`essid`.
374 If no match is found, it returns None.
376 for row in self.pstore:
377 if (row[0] == essid) and (row[1] == bssid):
378 return row.iter
379 return None
381 def on_network_selection(self, widget, data=None):
382 """ Enable/disable buttons based on the selected network.
383 :data:`widget` is the widget sending the signal and :data:`data`
384 is a list of arbitrary arguments, both are ignored.
386 store, selected_iter = self.selected_network.get_selected()
387 if selected_iter is None:
388 # No row is selected, disable all buttons except New.
389 # This occurs after a drag-and-drop.
390 self.edit_button.set_sensitive(False)
391 self.delete_button.set_sensitive(False)
392 self.connect_button.set_sensitive(False)
393 else:
394 # One row is selected, so enable or disable buttons.
395 self.connect_button.set_sensitive(True)
396 if store.get_value(selected_iter, 3):
397 # Known profile.
398 self.edit_button.set_sensitive(True)
399 self.delete_button.set_sensitive(True)
400 else:
401 # Unknown profile.
402 self.edit_button.set_sensitive(True)
403 self.delete_button.set_sensitive(False)
405 def show_about_info(self, widget, data=None):
406 """ Init and run the about dialog
408 Parameters:
410 'widget' -- gtk.Widget - The widget sending the event.
412 'data' -- tuple - list of arbitrary arguments (not used)
414 Returns:
416 nothing
418 about = transients.AboutDialog()
419 about.run()
420 about.destroy()
422 def edit_preferences(self, widget, data=None):
423 """ Init and run the preferences dialog
425 Parameters:
427 'widget' -- gtk.Widget - The widget sending the event.
429 'data' -- tuple - list of arbitrary arguments (not used)
431 Returns:
433 nothing
435 # get raw strings from config file
436 self.config.raw = True
437 prefs_editor = prefs.PreferencesEditor(self, self.config)
438 response = prefs_editor.run()
439 if response == int(gtk.RESPONSE_ACCEPT):
440 prefs_editor.save()
441 prefs_editor.destroy()
442 # get cooked strings from config file
443 self.config.raw = False
445 def add_profile(self, profile):
446 """ Add :data:`profile` to the list of APs shown to the user.
448 if profile['roaming']:
449 profile['bssid'] = ' Multiple APs'
451 wep = None
452 if profile['encrypted']:
453 wep = gtk.STOCK_DIALOG_AUTHENTICATION
455 self.pstore.append([profile['essid'], profile['bssid'],
456 known_profile_icon, profile['known'], profile['available'],
457 wep, signal_none_pb, profile['mode'], profile['protocol'],
458 profile['channel']])
460 def remove_profile(self, profile):
461 """ Remove :data:`profile` from the list of APs shown to the user.
463 if profile['roaming']:
464 prow_iter = self.get_row_by_ap(profile['essid'])
465 else:
466 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
467 if prow_iter is not None:
468 self.pstore.remove(prow_iter)
470 def create_new_profile(self, widget, profile, data=None):
471 """ Respond to a request to create a new AP profile
473 Parameters:
475 'widget' -- gtk.Widget - The widget sending the event.
477 'profile' -- dictionary - The AP profile to use as basis for new profile.
479 'data' -- tuple - list of arbitrary arguments (not used)
481 Returns:
483 boolean -- True if a profile was created and False if profile creation was canceled.
485 profile_editor = profile_ed.ProfileEditor(self, profile)
486 try:
487 profile = profile_editor.run()
488 except ValueError:
489 error_dlg = transients.ErrorDialog(profile_editor.dialog, "Cannot save empty ESSID")
490 del error_dlg
491 return False
492 finally:
493 profile_editor.destroy()
494 if profile:
495 store, selected_iter = self.plist.get_selection().get_selected()
496 if selected_iter is not None:
497 store.remove(selected_iter)
498 if profile['roaming']:
499 apname = misc.make_section_name(profile['essid'], '')
500 else:
501 apname = misc.make_section_name(profile['essid'], profile['bssid'])
502 # Check that the ap does not exist already
503 if apname in self.config.profiles():
504 error_dlg = transients.ErrorDialog(self.window, "A profile for %s already exists" % (apname))
505 del error_dlg
506 # try again
507 self.access_points[ apname ] = profile
508 self.config.set_section(apname, profile)
509 # if it is not in the auto_profile_order add it
510 if apname not in self.config.auto_profile_order:
511 self.config.auto_profile_order.insert(0, apname)
512 # add to the store
513 wep = None
514 if profile['encrypted']: wep = gtk.STOCK_DIALOG_AUTHENTICATION
515 try:
516 self.config.write()
517 except IOError as e:
518 if e.errno == errno.ENOENT:
519 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
520 del error_dlg
521 else:
522 raise e
523 # Add AP to the list displayed to user
524 if profile['roaming']:
525 ap_name = profile['essid'] + "\n" + ' Multiple APs'
526 while True:
527 prow_iter = self.get_row_by_ap(profile['essid'])
528 if prow_iter:
529 self.pstore.remove(prow_iter)
530 else:
531 break
532 else:
533 ap_name = profile['essid'] + "\n" + profile['bssid']
534 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']])
535 return True
536 else:
537 # Did not create new profile
538 return False
540 def edit_profile(self, widget, data=None):
541 """ Respond to a request to edit an AP profile. Edit selected AP profile if it
542 is known. Otherwise, create a new profile with the selected ESSID and BSSID.
544 Parameters:
546 'widget' -- gtk.Widget - The widget sending the event.
548 'data' -- tuple - list of arbitrary arguments (not used)
550 Returns:
552 nothing
554 store, selected_iter = self.plist.get_selection().get_selected()
555 if selected_iter is None:
556 # No AP is selected
557 return
559 essid = self.pstore.get_value(selected_iter, 0)
560 bssid = self.pstore.get_value(selected_iter, 1)
562 if bssid == ' Multiple APs':
563 # AP list says this is a roaming profile
564 apname = misc.make_section_name(essid, '')
565 else:
566 # AP list says this is NOT a roaming profile
567 apname = misc.make_section_name(essid, bssid)
568 profile = self.config.get_profile(apname)
569 if profile:
570 # A profile was found in the config file
571 profile['bssid'] = self.access_points[apname]['bssid']
572 profile_editor = profile_ed.ProfileEditor(self, profile)
573 try:
574 # try editing the profile
575 edited_profile = profile_editor.run()
576 except ValueError:
577 error_dlg = transients.ErrorDialog(profile_editor.dialog, "Cannot save empty ESSID")
578 del error_dlg
579 return False
580 finally:
581 # Always remove profile editor window from screen
582 profile_editor.destroy()
583 if edited_profile:
584 # A profile was returned by the editor
585 old_index = None
586 row = None
587 if edited_profile['essid'] != profile['essid'] or edited_profile['bssid'] != profile['bssid'] or edited_profile['roaming'] != profile['roaming']:
588 # ESSID, BSSID, or roaming was changed in profile editor
589 try:
590 self.commandQueue.put('pause')
591 self.commandQueue.join()
592 except queue.Full:
593 pass
594 if profile['roaming']:
595 # The old profile was a roaming profile
596 old_ap = misc.make_section_name(profile['essid'], '')
597 else:
598 # The old profile was NOT a roaming profile
599 old_ap = misc.make_section_name(profile['essid'], profile['bssid'])
600 # Find where old profile was in auto order
601 old_index = self.config.auto_profile_order.index(old_ap)
602 # Remove old profile and get its place in AP list
603 row = self.delete_profile(selected_iter, old_ap)
604 self.config.remove_section(old_ap)
605 try:
606 # Add AP to the list displayed to user
607 self.apQueue.put_nowait(edited_profile)
608 self.commandQueue.put('scan')
609 except queue.Full:
610 pass
611 if edited_profile['roaming']:
612 # New profile is a roaming profile
613 apname = misc.make_section_name(edited_profile['essid'], '')
614 ap_display = edited_profile['essid'] + "\n" + ' Multiple APs'
615 # Remove all other profiles that match the new profile ESSID
616 while True:
617 prow_iter = self.get_row_by_ap(edited_profile['essid'])
618 if prow_iter:
619 self.pstore.remove(prow_iter)
620 else:
621 break
622 else:
623 # New profile is NOT a roaming profile
624 apname = misc.make_section_name(edited_profile['essid'], edited_profile['bssid'])
625 ap_display = edited_profile['essid'] + "\n" + edited_profile['bssid']
626 # Insert the new profile in the same position as the one being replaced
627 if old_index is not None:
628 # Old profile was in auto order list
629 self.config.auto_profile_order.insert(old_index, apname)
630 if (( row is not None) and (self.pstore.iter_is_valid(row)) ):
631 self.pstore.insert_before(row, [ap_display, None, None, None, None, None, None, None, None])
632 self.access_points[apname] = edited_profile
633 self.config.set_section(apname, edited_profile)
634 try:
635 # Save updated profile to config file
636 self.config.write()
637 except IOError as e:
638 if e.errno == errno.ENOENT:
639 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
640 del error_dlg
641 else:
642 raise e
643 else:
644 # The AP does not already have a profile
645 profile = misc.get_new_profile()
646 profile['essid'] = self.pstore.get_value(selected_iter, 0)
647 profile['bssid'] = self.pstore.get_value(selected_iter, 1)
648 self.create_new_profile(widget, profile, data)
650 def delete_profile(self, selected_iter, apname):
651 """ Delete an AP profile (i.e. make profile unknown)
653 Parameters:
655 'selected_iter' -- gtk.TreeIter - The selected row.
657 'apname' -- string - The configuration file section to remove
659 Returns:
661 gtk.TreeIter -- the iter for the row removed from the gtk.ListStore
663 # Remove it
664 del self.access_points[apname]
665 self.config.remove_section(apname)
666 logger.info(apname)
667 if apname in self.config.auto_profile_order:
668 self.config.auto_profile_order.remove(apname)
669 self.pstore.remove(selected_iter)
670 # Let's save our current state
671 self.update_auto_profile_order()
672 try:
673 self.config.write()
674 except IOError as e:
675 if e.errno == errno.ENOENT:
676 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
677 del error_dlg
678 else:
679 raise e
680 return selected_iter
682 def delete_profile_with_check(self, widget, data=None):
683 """ Respond to a request to delete an AP profile (i.e. make profile unknown)
684 Check with user first.
686 Parameters:
688 'widget' -- gtk.Widget - The widget sending the event.
690 'data' -- tuple - list of arbitrary arguments (not used)
692 Returns:
694 nothing
696 store, selected_iter = self.plist.get_selection().get_selected()
697 if selected_iter is None:
698 return
700 essid = self.pstore.get_value(selected_iter, 0)
701 bssid = self.pstore.get_value(selected_iter, 1)
702 if bssid == ' Multiple APs':
703 apname = misc.make_section_name(essid, '')
704 else:
705 apname = misc.make_section_name(essid, bssid)
706 profile = self.config.get_profile(apname)
707 if profile['roaming']:
708 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, ))
709 else:
710 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))
711 known = store.get_value(selected_iter, 3)
712 if not known: return
713 res = dlg.run()
714 dlg.destroy()
715 del dlg
716 if res == gtk.RESPONSE_NO:
717 return
718 self.delete_profile(selected_iter, apname)
720 def connect_profile(self, widget, profile, data=None):
721 """ Respond to a request to connect to an AP.
723 Parameters:
725 'widget' -- gtk.Widget - The widget sending the event.
727 'profile' -- dictionary - The AP profile to which to connect.
729 'data' -- tuple - list of arbitrary arguments (not used)
731 Returns:
733 nothing
735 store, selected_iter = self.plist.get_selection().get_selected()
736 if selected_iter is None:
737 return
738 essid = self.pstore.get_value(selected_iter, 0)
739 bssid = self.pstore.get_value(selected_iter, 1)
740 known = store.get_value(selected_iter, 3)
741 if not known:
742 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?")
743 res = dlg.run()
744 dlg.destroy()
745 del dlg
746 if res == gtk.RESPONSE_NO:
747 return
748 profile = misc.get_new_profile()
749 profile['essid'] = essid
750 profile['bssid'] = bssid
751 if not self.create_new_profile(widget, profile, data):
752 return
753 else:
754 # Check for roaming profile.
755 ap_name = misc.make_section_name(essid, '')
756 profile = self.config.get_profile(ap_name)
757 if not profile:
758 # Check for normal profile.
759 ap_name = misc.make_section_name(essid, bssid)
760 profile = self.config.get_profile(ap_name)
761 if not profile:
762 # No configured profile
763 return
764 profile['bssid'] = self.access_points[ap_name]['bssid']
765 profile['channel'] = self.access_points[ap_name]['channel']
766 self.connection.connect_to_network(profile, self.status_window)
768 def disconnect_profile(self, widget, data=None):
769 """ Respond to a request to disconnect by calling ConnectionManager disconnect_interface().
771 Parameters:
773 'widget' -- gtk.Widget - The widget sending the event.
775 'data' -- tuple - list of arbitrary arguments (not used)
777 Returns:
779 nothing
781 if data == "cancel":
782 self.status_window.update_message("Canceling connection...")
783 if sys.modules.has_key("gtk"):
784 while gtk.events_pending():
785 gtk.main_iteration(False)
786 self.connection.disconnect_interface()
788 def profile_order_updater(self, model, path, iter, auto_profile_order):
791 if model.get_value(iter, 3) is True:
792 essid = self.pstore.get_value(selected_iter, 0)
793 bssid = self.pstore.get_value(selected_iter, 1)
794 if bssid == ' Multiple APs':
795 bssid = ''
796 apname = misc.make_section_name(essid, bssid)
797 auto_profile_order.append(apname)
799 def update_auto_profile_order(self, widget=None, data=None, data2=None):
800 """ Update the config file auto profile order from the on-screen order
802 Parameters:
804 'widget' -- gtk.Widget - The widget sending the event.
806 'data' -- tuple - list of arbitrary arguments (not used)
808 'data2' -- tuple - list of arbitrary arguments (not used)
810 Returns:
812 nothing
814 # recreate the auto_profile_order
815 auto_profile_order = []
816 self.pstore.foreach(self.profile_order_updater, auto_profile_order)
817 self.config.auto_profile_order = auto_profile_order
818 try:
819 self.config.write()
820 except IOError as e:
821 if e.errno == errno.ENOENT:
822 error_dlg = transients.ErrorDialog(self.window, "Could not save configuration file:\n%s\n\n%s" % (self.config.filename, e.strerror))
823 del error_dlg
824 else:
825 raise e
828 # Make so we can be imported
829 if __name__ == "__main__":
830 pass