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