Split profile editing across UI and core logic
[wifi-radar.git] / wifiradar / gui / g2 / __init__.py
blob124e81b4ed5f0fb15beab939f0ab93f9a2f2477a
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.msg_pipe = msg_pipe
99 gtk.gdk.threads_init()
101 self.icon = gtk.gdk.pixbuf_new_from_file("pixmaps/wifi-radar.png")
103 self.window = gtk.Dialog('WiFi Radar', None, gtk.DIALOG_MODAL)
104 self.window.set_icon(self.icon)
105 self.window.set_border_width(10)
106 self.window.set_size_request(550, 300)
107 self.window.set_title("WiFi Radar")
108 self.window.connect('delete_event', self.delete_event)
109 # let's create all our widgets
110 self.current_network = gtk.Label()
111 self.current_network.set_property('justify', gtk.JUSTIFY_CENTER)
112 self.current_network.show()
113 self.close_button = gtk.Button("Close", gtk.STOCK_CLOSE)
114 self.close_button.show()
115 self.close_button.connect('clicked', self.delete_event, None)
116 self.about_button = gtk.Button("About", gtk.STOCK_ABOUT)
117 self.about_button.show()
118 self.about_button.connect('clicked', self.show_about_info, None)
119 self.preferences_button = gtk.Button("Preferences", gtk.STOCK_PREFERENCES)
120 self.preferences_button.show()
121 self.preferences_button.connect('clicked', self.edit_preferences, None)
122 # essid bssid known_icon known available wep_icon signal_level mode protocol channel
123 self.pstore = gtk.ListStore(str, str, gtk.gdk.Pixbuf, bool, bool, str, gtk.gdk.Pixbuf, str, str, str)
124 self.plist = gtk.TreeView(self.pstore)
125 # The icons column, known and encryption
126 self.pix_cell = gtk.CellRendererPixbuf()
127 self.wep_cell = gtk.CellRendererPixbuf()
128 self.icons_cell = gtk.CellRendererText()
129 self.icons_col = gtk.TreeViewColumn()
130 self.icons_col.pack_start(self.pix_cell, False)
131 self.icons_col.pack_start(self.wep_cell, False)
132 self.icons_col.add_attribute(self.pix_cell, 'pixbuf', 2)
133 self.icons_col.add_attribute(self.wep_cell, 'stock-id', 5)
134 self.plist.append_column(self.icons_col)
135 # The AP column
136 self.ap_cell = gtk.CellRendererText()
137 self.ap_col = gtk.TreeViewColumn("Access Point")
138 self.ap_col.pack_start(self.ap_cell, True)
139 self.ap_col.set_cell_data_func(self.ap_cell, self._set_ap_col_value)
140 self.plist.append_column(self.ap_col)
141 # The signal column
142 self.sig_cell = gtk.CellRendererPixbuf()
143 self.signal_col = gtk.TreeViewColumn("Signal")
144 self.signal_col.pack_start(self.sig_cell, True)
145 self.signal_col.add_attribute(self.sig_cell, 'pixbuf', 6)
146 self.plist.append_column(self.signal_col)
147 # The mode column
148 self.mode_cell = gtk.CellRendererText()
149 self.mode_col = gtk.TreeViewColumn("Mode")
150 self.mode_col.pack_start(self.mode_cell, True)
151 self.mode_col.add_attribute(self.mode_cell, 'text', 7)
152 self.plist.append_column(self.mode_col)
153 # The protocol column
154 self.prot_cell = gtk.CellRendererText()
155 self.protocol_col = gtk.TreeViewColumn("802.11")
156 self.protocol_col.pack_start(self.prot_cell, True)
157 self.protocol_col.add_attribute(self.prot_cell, 'text', 8)
158 self.plist.append_column(self.protocol_col)
159 # The channel column
160 self.channel_cell = gtk.CellRendererText()
161 self.channel_col = gtk.TreeViewColumn("Channel")
162 self.channel_col.pack_start(self.channel_cell, True)
163 self.channel_col.add_attribute(self.channel_cell, 'text', 9)
164 self.plist.append_column(self.channel_col)
165 # DnD Ordering
166 self.plist.set_reorderable(True)
167 # detect d-n-d of AP in round-about way, since rows-reordered does not work as advertised
168 self.pstore.connect('row-deleted', self.update_auto_profile_order)
169 # enable/disable buttons based on the selected network
170 self.selected_network = self.plist.get_selection()
171 self.selected_network.connect('changed', self.on_network_selection, None)
172 # the list scroll bar
173 sb = gtk.VScrollbar(self.plist.get_vadjustment())
174 sb.show()
175 self.plist.show()
176 # Add New button
177 self.new_button = gtk.Button("_New")
178 self.new_button.connect('clicked', self.create_new_profile, misc.get_new_profile())
179 self.new_button.show()
180 # Add Configure button
181 self.edit_button = gtk.Button("C_onfigure")
182 self.edit_button.connect('clicked', self.edit_profile, None)
183 self.edit_button.show()
184 self.edit_button.set_sensitive(False)
185 # Add Delete button
186 self.delete_button = gtk.Button("_Delete")
187 self.delete_button.connect('clicked', self.delete_profile_with_check, None)
188 self.delete_button.show()
189 self.delete_button.set_sensitive(False)
190 # Add Connect button
191 self.connect_button = gtk.Button("Co_nnect")
192 self.connect_button.connect('clicked', self.connect_profile, None)
193 # Add Disconnect button
194 self.disconnect_button = gtk.Button("D_isconnect")
195 self.disconnect_button.connect('clicked', self.disconnect_profile, None)
196 # lets add our widgets
197 rows = gtk.VBox(False, 3)
198 net_list = gtk.HBox(False, 0)
199 listcols = gtk.HBox(False, 0)
200 prows = gtk.VBox(False, 0)
201 # lets start packing
202 # the network list
203 net_list.pack_start(self.plist, True, True, 0)
204 net_list.pack_start(sb, False, False, 0)
205 # the rows level
206 rows.pack_start(net_list , True, True, 0)
207 rows.pack_start(self.current_network, False, True, 0)
208 # the list columns
209 listcols.pack_start(rows, True, True, 0)
210 listcols.pack_start(prows, False, False, 5)
211 # the list buttons
212 prows.pack_start(self.new_button, False, False, 2)
213 prows.pack_start(self.edit_button, False, False, 2)
214 prows.pack_start(self.delete_button, False, False, 2)
215 prows.pack_end(self.connect_button, False, False, 2)
216 prows.pack_end(self.disconnect_button, False, False, 2)
218 self.window.action_area.pack_start(self.about_button)
219 self.window.action_area.pack_start(self.preferences_button)
220 self.window.action_area.pack_start(self.close_button)
222 rows.show()
223 prows.show()
224 listcols.show()
225 self.window.vbox.add(listcols)
226 self.window.vbox.set_spacing(3)
227 self.window.show_all()
229 # Now, immediately hide these two. The proper one will be
230 # displayed later, based on interface state. -BEF-
231 self.disconnect_button.hide()
232 self.connect_button.hide()
233 self.connect_button.set_sensitive(False)
235 # set up status window for later use
236 self.status_window = transients.StatusWindow(self)
237 self.status_window.cancel_button.connect('clicked', self.disconnect_profile, "cancel")
239 self._running = True
240 # Check for incoming messages every 25 ms, a.k.a. 40 Hz.
241 glib.timeout_add(25, self.run)
243 gtk.main()
245 def run(self):
246 """ Watch for incoming messages.
248 if self.msg_pipe.poll():
249 try:
250 msg = self.msg_pipe.recv()
251 except (EOFError, IOError) as e:
252 # This is bad, really bad.
253 logger.critical('read on closed ' +
254 'Pipe ({}), failing...'.format(rfd))
255 raise misc.PipeError(e)
256 else:
257 self._check_message(msg)
258 return self._running
260 def _check_message(self, msg):
261 """ Process incoming messages.
263 if msg.topic == 'EXIT':
264 self.delete_event()
265 elif msg.topic == 'CONFIG-UPDATE':
266 # Replace configuration manager with the one in msg.details.
267 self.config = msg.details
268 elif msg.topic == 'PROFILE-UPDATE':
269 self.update_plist_items(msg.details)
270 elif msg.topic == 'PROFILE-UNLIST':
271 self.remove_profile(msg.details)
272 elif msg.topic == 'PROFILE-MOVE':
273 profile, new_position = msg.details
274 old_position = self.get_row_by_ap(profile['essid'],
275 profile['bssid'])
276 self.pstore.move_before(old_position, new_position)
277 elif msg.topic == 'PREFS-EDIT':
278 self.edit_preferences()
279 else:
280 logger.warning('unrecognized Message: "{}"'.format(msg))
282 def destroy(self, widget=None):
283 """ Quit the Gtk event loop. :data:`widget` is the widget
284 sending the signal, but it is ignored.
286 if self.status_window:
287 self.status_window.destroy()
288 gtk.main_quit()
290 def delete_event(self, widget=None, data=None):
291 """ Shutdown the application. :data:`widget` is the widget sending
292 the signal and :data:`data` is a list of arbitrary arguments,
293 both are ignored. Always returns False to not propigate the
294 signal which called :func:`delete_event`.
296 self._running = False
297 self.msg_pipe.send(Message('EXIT', ''))
298 self.msg_pipe.close()
299 self.window.hide()
300 # process GTK events so that window hides more quickly
301 if sys.modules.has_key("gtk"):
302 while gtk.events_pending():
303 gtk.main_iteration(False)
304 self.destroy()
305 return False
307 def update_network_info(self, profile=None, ip=None):
308 """ Update the current ip and essid shown to the user.
310 if (profile is None) and (ip is None):
311 self.current_network.set_text("Not Connected.")
312 else:
313 self.current_network.set_text('Connected to {}\nIP Address {}'.format(profile, ip))
315 def update_connect_buttons(self, connected=False):
316 """ Set the state of connect/disconnect buttons to reflect the
317 current connected state.
319 if connected:
320 self.connect_button.hide()
321 self.disconnect_button.show()
322 else:
323 self.disconnect_button.hide()
324 self.connect_button.show()
326 def update_plist_items(self, profile):
327 """ Updates the display of :data:`profile`.
329 if profile['roaming']:
330 prow_iter = self.get_row_by_ap(profile['essid'])
331 else:
332 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
334 if prow_iter is None:
335 # the AP is not in the list of APs on the screen
336 self.add_profile(profile)
337 else:
338 # the AP is in the list of APs on the screen
339 wep = None
340 if profile['encrypted']:
341 wep = gtk.STOCK_DIALOG_AUTHENTICATION
342 # Update the Gtk objects.
343 self.pstore.set_value(prow_iter, 2, pixbuf_from_known(profile['known']))
344 self.pstore.set_value(prow_iter, 3, profile['known'])
345 self.pstore.set_value(prow_iter, 4, profile['available'])
346 self.pstore.set_value(prow_iter, 5, wep)
347 self.pstore.set_value(prow_iter, 6, pixbuf_from_signal(profile['signal']))
348 self.pstore.set_value(prow_iter, 7, profile['mode'])
349 self.pstore.set_value(prow_iter, 8, profile['protocol'])
350 self.pstore.set_value(prow_iter, 9, profile['channel'])
352 def _set_ap_col_value(self, column, cell, model, iter):
353 """ Set the text attribute of :data:`column` to the first two
354 :data:`model` values joined by a newline. This is for
355 displaying the :data:`essid` and :data:`bssid` in a single
356 cell column.
358 essid = model.get_value(iter, 0)
359 bssid = model.get_value(iter, 1)
360 cell.set_property('text', '\n'.join([essid, bssid]))
362 def get_row_by_ap(self, essid, bssid=' Multiple APs'):
363 """ Returns a :class:`gtk.TreeIter` for the row which holds
364 :data:`essid` and :data:`bssid`.
366 :data:`bssid` is optional. If not given, :func:`get_row_by_ap`
367 will try to match a roaming profile with the given :data:`essid`.
369 If no match is found, it returns None.
371 for row in self.pstore:
372 if (row[0] == essid) and (row[1] == bssid):
373 return row.iter
374 return None
376 def on_network_selection(self, widget=None, data=None):
377 """ Enable/disable buttons based on the selected network.
378 :data:`widget` is the widget sending the signal and :data:`data`
379 is a list of arbitrary arguments, both are ignored.
381 store, selected_iter = self.selected_network.get_selected()
382 if selected_iter is None:
383 # No row is selected, disable all buttons except New.
384 # This occurs after a drag-and-drop.
385 self.edit_button.set_sensitive(False)
386 self.delete_button.set_sensitive(False)
387 self.connect_button.set_sensitive(False)
388 else:
389 # One row is selected, so enable or disable buttons.
390 self.connect_button.set_sensitive(True)
391 if store.get_value(selected_iter, 3):
392 # Known profile.
393 self.edit_button.set_sensitive(True)
394 self.delete_button.set_sensitive(True)
395 else:
396 # Unknown profile.
397 self.edit_button.set_sensitive(True)
398 self.delete_button.set_sensitive(False)
400 def show_about_info(self, widget=None, data=None):
401 """ Handle the life-cycle of the About dialog. :data:`widget` is
402 the widget sending the signal and :data:`data` is a list of
403 arbitrary arguments, both are ignored.
405 about = transients.AboutDialog()
406 about.run()
407 about.destroy()
409 def edit_preferences(self, widget=None, data=None):
410 """ Handle the life-cycle of the preferences dialog. :data:`widget`
411 is the widget sending the signal and :data:`data` is a list of
412 arbitrary arguments, both are ignored.
414 # get raw strings from config file
415 self.config.raw = True
416 prefs_editor = prefs.PreferencesEditor(self, self.config)
417 response = prefs_editor.run()
418 if response == int(gtk.RESPONSE_ACCEPT):
419 prefs_editor.save()
420 prefs_editor.destroy()
421 # get cooked strings from config file
422 self.config.raw = False
424 def add_profile(self, profile):
425 """ Add :data:`profile` to the list of APs shown to the user.
427 if profile['roaming']:
428 profile['bssid'] = ' Multiple APs'
430 wep = None
431 if profile['encrypted']:
432 wep = gtk.STOCK_DIALOG_AUTHENTICATION
434 self.pstore.append([profile['essid'], profile['bssid'],
435 known_profile_icon, profile['known'], profile['available'],
436 wep, signal_none_pb, profile['mode'], profile['protocol'],
437 profile['channel']])
439 def remove_profile(self, profile):
440 """ Remove :data:`profile` from the list of APs shown to the user.
442 if profile['roaming']:
443 prow_iter = self.get_row_by_ap(profile['essid'])
444 else:
445 prow_iter = self.get_row_by_ap(profile['essid'], profile['bssid'])
446 if prow_iter is not None:
447 self.pstore.remove(prow_iter)
449 def create_new_profile(self, widget=None, profile=None, data=None):
450 """ Respond to a user request to create a new AP profile.
451 :data:`widget` is the widget sending the signal. :data:profile`
452 is an AP profile to use as the basis for the new profile. It
453 is likely empty or mostly empty. :data:`data` is a list of
454 arbitrary arguments. :data:`widget` and "data"`data` are both
455 ignored.
457 The order of parameters is important. Because when this method
458 is called from a signal handler, :data:`widget` is always the
459 first argument.
461 profile_editor = profile_ed.ProfileEditor(self, profile)
462 try:
463 profile = profile_editor.run()
464 except ValueError:
465 self.msg_pipe.send(Message('ERROR', 'Cannot save empty ESSID'))
466 else:
467 if profile:
468 self.msg_pipe.send(Message('PROFILE-UPDATE', profile))
469 finally:
470 profile_editor.destroy()
472 def request_profile_edit(self, widget=None, data=None):
473 """ Respond to a request to edit an AP profile. Edit the selected
474 AP profile, if it is known. Otherwise, create a new profile
475 with the selected ESSID and BSSID. :data:`widget` is the widget
476 sending the signal and :data:`data` is a list of arbitrary
477 arguments, both are ignored.
479 store, selected_iter = self.plist.get_selection().get_selected()
480 if selected_iter is None:
481 # No AP is selected
482 return
484 essid = self.pstore.get_value(selected_iter, 0)
485 bssid = self.pstore.get_value(selected_iter, 1)
487 if bssid == ' Multiple APs':
488 # AP list says this is a roaming profile
489 apname = misc.make_section_name(essid, '')
490 else:
491 # AP list says this is NOT a roaming profile
492 apname = make_section_name(essid, bssid)
493 self.msg_pipe(Message('PROFILE-EDIT-REQUEST', apname))
495 def edit_profile(self, profile):
496 """ Allow the user to edit :data:`profile`.
498 profile_editor = profile_ed.ProfileEditor(self, profile)
499 try:
500 # Try editing the profile.
501 edited_profile = profile_editor.run()
502 except ValueError:
503 self.msg_pipe.send(Message('ERROR', 'Cannot save empty ESSID'))
504 else:
505 # A profile was returned by the editor.
506 self.msg_pipe.send(Message('SCAN-STOP', ''))
507 # Replace old profile.
508 self.msg_pipe.send(Message('PROFILE-REPLACE',
509 (edited_profile, profile)))
510 self.msg_pipe.send(Message('SCAN-START', ''))
511 # Request saving the configuration file after the update.
512 self.msg_pipe.send(Message('CONFIG-SAVE', ''))
513 finally:
514 # Always remove profile editor window from screen
515 profile_editor.destroy()
517 def delete_profile(self, apname):
518 """ Delete the profile associated with :data:`apname` (i.e. make
519 the profile unknown).
521 self.msg_pipe.send(Message('PROFILE-REMOVE', apname))
523 def delete_profile_with_check(self, widget=None, data=None):
524 """ Respond to a request to delete an AP profile (i.e. make the
525 profile unknown) Check with the user first. :data:`widget`
526 is the widget sending the signal and :data:`data` is a list of
527 arbitrary arguments, both are ignored.
529 store, selected_iter = self.plist.get_selection().get_selected()
530 if selected_iter is None:
531 return
533 essid = self.pstore.get_value(selected_iter, 0)
534 bssid = self.pstore.get_value(selected_iter, 1)
535 if bssid == ' Multiple APs':
536 apname = misc.make_section_name(essid, '')
537 else:
538 apname = misc.make_section_name(essid, bssid)
539 profile = self.config.get_profile(apname)
540 if profile['roaming']:
541 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, ))
542 else:
543 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))
544 known = store.get_value(selected_iter, 3)
545 if not known: return
546 res = dlg.run()
547 dlg.destroy()
548 del dlg
549 if res == gtk.RESPONSE_NO:
550 return
551 self.delete_profile(apname)
553 def connect_profile(self, widget, profile, data=None):
554 """ Respond to a request to connect to an AP.
556 Parameters:
558 'widget' -- gtk.Widget - The widget sending the event.
560 'profile' -- dictionary - The AP profile to which to connect.
562 'data' -- tuple - list of arbitrary arguments (not used)
564 Returns:
566 nothing
568 store, selected_iter = self.plist.get_selection().get_selected()
569 if selected_iter is None:
570 return
571 essid = self.pstore.get_value(selected_iter, 0)
572 bssid = self.pstore.get_value(selected_iter, 1)
573 known = store.get_value(selected_iter, 3)
574 if not known:
575 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?")
576 res = dlg.run()
577 dlg.destroy()
578 del dlg
579 if res == gtk.RESPONSE_NO:
580 return
581 profile = misc.get_new_profile()
582 profile['essid'] = essid
583 profile['bssid'] = bssid
584 if not self.create_new_profile(widget, profile, data):
585 return
586 else:
587 # Check for roaming profile.
588 ap_name = misc.make_section_name(essid, '')
589 profile = self.config.get_profile(ap_name)
590 if not profile:
591 # Check for normal profile.
592 ap_name = misc.make_section_name(essid, bssid)
593 profile = self.config.get_profile(ap_name)
594 if not profile:
595 # No configured profile
596 return
597 profile['bssid'] = self.access_points[ap_name]['bssid']
598 profile['channel'] = self.access_points[ap_name]['channel']
599 self.msg_pipe.send(Message('CONNECT', profile))
601 def disconnect_profile(self, widget=None, data=None):
602 """ Respond to a request to disconnect by sending a message to
603 ConnectionManager. :data:`widget` is the widget sending the
604 signal and :data:`data` is a list of arbitrary arguments, both
605 are ignored.
607 self.msg_pipe.send(Message('DISCONNECT', ''))
608 if data == "cancel":
609 self.status_window.update_message("Canceling connection...")
610 if sys.modules.has_key("gtk"):
611 while gtk.events_pending():
612 gtk.main_iteration(False)
614 def profile_order_updater(self, model, path, iter, auto_profile_order):
617 if model.get_value(iter, 3) is True:
618 essid = self.pstore.get_value(selected_iter, 0)
619 bssid = self.pstore.get_value(selected_iter, 1)
620 if bssid == ' Multiple APs':
621 bssid = ''
622 apname = misc.make_section_name(essid, bssid)
623 auto_profile_order.append(apname)
625 def update_auto_profile_order(self, widget=None, data=None, data2=None):
626 """ Update the config file auto profile order from the on-screen
627 order. :data:`widget` is the widget sending the signal and
628 :data:`data` and :data:`data2` is a list of arbitrary arguments,
629 all are ignored.
631 # recreate the auto_profile_order
632 auto_profile_order = []
633 self.pstore.foreach(self.profile_order_updater, auto_profile_order)
634 self.msg_pipe.send(Message('PROFILE-ORDER-UPDATE', auto_profile_order))
635 # Request saving the configuration file after the update.
636 self.msg_pipe.send(Message('CONFIG-SAVE', ''))
639 # Make so we can be imported
640 if __name__ == "__main__":
641 pass