Below is the file 'interapplet.py' from this revision. You can also download the file.
#!/usr/bin/python import os import sys import re import pygtk pygtk.require('2.0') import gtk import gnomeapplet import gobject import gamin import popen2 import fcntl import pipes state_directory = "/var/lib/interapplet/state" ifstate_file = '/etc/network/ifstate' interfaces_file = '/etc/network/interfaces' update_state_program = '/usr/lib/interapplet/set-state.sh' normal_logo = '/usr/share/interapplet/normal.png' active_logo = '/usr/share/interapplet/active.gif' show_log_xml = '''\ <popup name="button3"> <menuitem name="log_item" verb="log_verb_show" label="_Show log" /> <menuitem name="interapplet_about" verb="interapplet_about" _label="_About" pixtype="stock" pixname="gnome-stock-about"/> </popup>''' def set_nonblocking(fd): fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) class InterfaceInfo: def __init__(self, file): self.maps = {} self.dialups = set() self.interfaces = set() self.mappings = set() self.mappable = set() # FIXME this will never update, which is awful exclude = set(('gre0', 'lo', 'tunl0', 'dummy0')) for line in open('/proc/net/dev'): m = re.match(r' *([^:]+):', line) if m: ifname = m.groups()[0].strip() if ifname not in exclude: self.mappable.add(ifname) fd = open(file) mapping = re.compile(r'^\s?mapping\s+([\w_:]+)') map = re.compile(r'^\s?map\s+([\w_]+)\s+([\w_]+)') auto = re.compile(r'^\s?auto[\s^]') iface = re.compile(r'^\s?iface\s+([\w_]+)\s+([\w_]+)\s+([\w_]+)') current_mapping = None for line in fd: m = mapping.match(line) if m: current_mapping = m.groups()[0] self.mappings.add(current_mapping) self.maps[current_mapping] = [] if current_mapping: m = map.match(line) if m: self.maps[current_mapping].append(m.groups()) m = iface.match(line) if m: current_mapping = None name, family, method = m.groups() if method == "loopback": continue elif method == "ppp" or method == "wvdial": self.dialups.add(name) else: self.interfaces.add(name) m = auto.match(line) if m: current_mapping = None continue class InterfaceState: def __init__(self, file): self.state = {} mapping = re.compile(r'^([\w_]+)=([\w_]+)$') for line in open(file): m = mapping.match(line) if m: self.state[m.groups()[0]] = m.groups()[1] class InterfaceApplet(gnomeapplet.Applet): def __init__(self, applet, iid): self.run_queue = [] self.currently_running = None self.applet = applet self.applet.connect("delete-event", self.cleanup) self.box = gtk.EventBox() self.icon = gtk.Image() self.set_normal_logo() self.box.connect("button-press-event", self.icon_clicked_cb) self.box.add(self.icon) self.applet.add(self.box) self.applet.show_all() self.applet.setup_menu(show_log_xml, [('log_verb_show', self.log_verb_cb), ('interapplet_about', self.about_cb)]) self.tooltips = gtk.Tooltips() self.set_applet_tip(None) self.log_window = gtk.Window() self.log_window.resize(600,150) self.log_window.set_title("Interface Applet Log") self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.text_view = gtk.TextView() self.text_view.set_editable(False) self.text_view.set_cursor_visible(False) self.scrolled_window.add(self.text_view) self.scrolled_window.show_all() self.log_window_x = None self.log_window_y = None self.log_window.add(self.scrolled_window) self.log_window.connect("delete-event", self.log_window.hide_on_delete) self.log_window_hide() # load interface state and update it using gamin self.if_info = InterfaceInfo(interfaces_file) self.if_state = InterfaceState(ifstate_file) self.menu = self.build_menu() # disabled by [GMB] # causes irrating prompt when first starting program, and is probably # not needed. #self.init_state_files() self.mon = gamin.WatchMonitor() self.mon.watch_file(ifstate_file, self.interfaces_changed_cb) self.mon.watch_file(interfaces_file, self.interfaces_changed_cb) # watch gamin for events gobject.io_add_watch(self.mon.get_fd(), gobject.IO_IN, self.mon_cb) def cleanup(self): # some awful un-python thing we have to do to stop memory leaks del self.applet gtk.main_quit() def set_normal_logo(self): self.icon.set_from_file(normal_logo) def set_active_logo(self): self.icon.set_from_file(active_logo) def update_state(self, mapping, interface): command = "%s %s %s" % (update_state_program, pipes.quote(mapping), pipes.quote(interface)) self.run_command("/usr/bin/gksudo %s" % (pipes.quote(command))) self.log_append_text("\n*** Mapping '%s' now mapped to '%s'\n" % (mapping, interface)) def log_window_hide(self): self.log_window_x, self.log_window_y = self.log_window.get_position() self.log_window.hide() def log_window_show(self): # if self.log_window_x != None and self.log_window_y != None: # self.log_window.move(self.log_window_x, self.log_window_y) self.log_window.show() def log_verb_cb(self, applet, verb): if verb == "log_verb_show": self.log_window_show() elif verb == "log_verb_hide": self.log_window_hide() def log_append_text(self, text): buffer = self.text_view.get_buffer() iter = buffer.get_end_iter() buffer.insert(iter, text) iter = buffer.get_end_iter() self.text_view.scroll_to_iter(iter, 0) def child_data_cb(self, source, condition, user_data=None): data = source.read() if len(data) == 0: # EOF source.close() return False else: self.log_append_text(data) return True def wait_cb(self): # if there is no process running, then nothing to do if self.currently_running == None: return False command, pid = self.currently_running exited_pid, exited_status = os.waitpid(pid, os.WNOHANG) if exited_pid == pid: self.log_append_text("\n*** Command '%s' (%d) has exit with status: %d\n" % (command, pid, exited_status)) self.set_normal_logo() self.currently_running = None self.set_applet_tip(None) self.run_command(None) return False else: # it hasn't quit yet; around we go again return True def set_applet_tip(self, tip): if tip == None: self.tooltips.disable() else: self.tooltips.set_tip(self.applet, tip) self.tooltips.enable() def run_command(self, command): # we only wish to have one command running at any time. # so, firstly this function checks: is self.currently_running None? # if not, append to the run_queue and return # if so, start the command we've been passed # set a periodic timer to try and waitpid() for that command # that timer could do animation! # when waitpid() finds the process, we check the queue and run the # next thing. if command != None: self.run_queue.append(command) if len(self.run_queue) == 0: return if self.currently_running != None: # there is already a command running; # we have appended to the queue. nothing more to do. return to_run, self.run_queue = self.run_queue[0], self.run_queue[1:] # open a pipe to a process. set things up so that we append any output / input from # the child to our vte buffer or whatever. p = popen2.Popen3(to_run, capturestderr=True) set_nonblocking(p.fromchild) set_nonblocking(p.childerr) p.tochild.close() self.currently_running = (to_run, p.pid) gobject.io_add_watch(p.fromchild, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP, self.child_data_cb) gobject.io_add_watch(p.childerr, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP, self.child_data_cb) # start a callback to watch for this command exiting. self.log_append_text("\n*** Command '%s' (%d) is now running.\n" % (to_run, p.pid)) self.set_applet_tip("Command running: %s" % (to_run)) self.set_active_logo() gobject.timeout_add(500, self.wait_cb) def disable_interface(self, int): command = '/sbin/ifdown %s' % (pipes.quote(int)) self.run_command("/usr/bin/gksudo %s" % (pipes.quote(command))) def enable_interface(self, int, mapping=None): command = '/sbin/ifup ' + pipes.quote(int) if mapping != None: command += "=" + pipes.quote(mapping) self.run_command("/usr/bin/gksudo %s" % (pipes.quote(command))) def init_state_files(self): for mapping in self.if_state.state: if not mapping in self.if_info.mappings: continue self.update_state(mapping, self.if_state.state[mapping]) def mon_cb(self, source, condition, user_data=None): self.mon.handle_events() return True def interfaces_changed_cb(self, path, event): # we only care about the file getting created, or the file # being modified if event != gamin.GAMChanged and event != gamin.GAMCreated: return if path == ifstate_file: self.if_state = InterfaceState(ifstate_file) elif path == interfaces_file: self.if_info = InterfaceInfo(interfaces_file) self.menu = self.build_menu() def radio_menu_action_cb(self, widget, event, param): # ok, a button was pressed on our radio button # we want the UI to be driven by the gamin events, not by # the user.. until we _actually_ enter this new state # leave the UI as it was # # we return True from this callback to prevent the radio # button from updating. self.menu_action_cb(widget, param) # we have to do this manually; we've blocked # the normal way for it to happen self.menu.popdown() return True def menu_action_cb(self, item, param): apply_to, action = param[:2] params = param[2:] if action == "disable": self.disable_interface(apply_to) elif action == "enable": self.enable_interface(apply_to) elif action == "interface": new_interface = params[0] current_interface = self.if_state.state.get(action) if new_interface == current_interface: return self.disable_interface(apply_to) self.update_state(apply_to, params[0]) self.enable_interface(apply_to, params[0]) def build_mapping_menu(self, mapping): menu = gtk.Menu() explicit = map(lambda x: x[0], self.if_info.maps.get(mapping, [])) interfaces = list(self.if_info.interfaces) interfaces.sort() group = None current_interface = self.if_state.state.get(mapping) # add a special "disable" option if we are enabled if current_interface != None: item = gtk.MenuItem("Disable") item.connect("activate", self.menu_action_cb, (mapping, "disable")) item.show() menu.append(item) else: item = gtk.MenuItem("Not enabled") item.show() item.set_sensitive(False) menu.append(item) # then a separator item = gtk.SeparatorMenuItem() item.show() menu.append(item) # add options to change our selected network if len(interfaces) == 0: item = gtk.ImageMenuItem("No interfaces to map to. See README file.") item.set_sensitive(False) item.show() menu.append(item) for interface in interfaces: item = gtk.RadioMenuItem(group, interface, use_underline=False) if interface == current_interface: item.set_active(True) if group == None: group = item item.connect("button-release-event", self.radio_menu_action_cb, (mapping, "interface", interface)) item.show() menu.append(item) return menu def build_dialup_menu(self, dialup): menu = gtk.Menu() if self.if_state.state.has_key(dialup): item = gtk.MenuItem("Disconnect") item.show() item.connect("activate", self.menu_action_cb, (dialup, "disable")) menu.append(item) else: item = gtk.MenuItem("Connect") item.connect("activate", self.menu_action_cb, (dialup, "enable")) item.show() menu.append(item) return menu def build_menu(self): menu = gtk.Menu() mappings = list(self.if_info.mappable) mappings.sort() nin = gtk.Image() if len(mappings) == 0: item = gtk.ImageMenuItem("No mapable interfaces found.") item.set_sensitive(False) item.show() menu.append(item) for mapping in mappings: self.build_mapping_menu(mapping) item = gtk.ImageMenuItem(mapping) item.set_submenu(self.build_mapping_menu(mapping)) item.show() menu.append(item) dialups = list(self.if_info.dialups) if len(dialups) > 0: item = gtk.SeparatorMenuItem() item.show() menu.append(item) for dialup in dialups: self.build_dialup_menu(dialup) item = gtk.MenuItem(dialup) item.set_submenu(self.build_dialup_menu(dialup)) item.show() menu.append(item) return menu def position_popup_cb(self, widget): x, y = self.applet.window.get_origin() applet_width = self.applet.allocation.width applet_height = self.applet.allocation.height orientation = self.applet.get_orient() if orientation == gnomeapplet.ORIENT_UP: y -= applet_height elif orientation == gnomeapplet.ORIENT_DOWN: y += applet_height elif orientation == gnomeapplet.ORIENT_LEFT: x -= applet_width elif orientation == gnomeapplet.ORIENT_RIGHT: x += applet_width return (x, y, True) def icon_clicked_cb(self, widget, event, user=None): if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: self.menu.popup(None,None,self.position_popup_cb,event.button,event.time) return False def about_cb(self, widget, user=None): about = gtk.AboutDialog() about.set_name("InterApplet") about.set_version("0.01") about.set_copyright("Copyright 2005 Grahame Bowland") about.set_website("http://grahame.angrygoats.net/interapplet.shtml") about.set_authors(["Grahame Bowland"]) about.set_license("""\ InterApplet Copyright (C) 2005 Grahame Bowland This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """) about.run() about.destroy() def interface_applet_factory(applet, iid): InterfaceApplet(applet, iid) return gtk.TRUE if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'run-in-window': main_window = gtk.Window(gtk.WINDOW_TOPLEVEL) main_window.set_title('Network Location Applet') main_window.connect('destroy', gtk.main_quit) app = gnomeapplet.Applet() interface_applet_factory(app, None) app.reparent(main_window) main_window.show_all() gtk.main() else: gnomeapplet.bonobo_factory("OAFIID:GNOME_InterApplet_Factory", gnomeapplet.Applet.__gtype__, "InterApplet", "0", interface_applet_factory) sys.exit()