As part of the effort to support linux users connecting to our IPSEC/L2TP based VPN, I developed my first ever gnome panel applet written in Python (another first for me). I have a functional bash script that can be used to connect to/disconnect from the VPN, but I wanted an easier way for users to interact with the script. As a result, I developed the applet to display connection status, prompt for connection credentials and to manage the connection process so users don’t have to open a terminal and work at the command line.
The vpn shell script indicates status through a /var/lock/subsys/VPN file. The applet below constantly polls status by checking for the existence of this file (see the status_timeout method). Once the user has clicked on the applet icon, and entered the appropriate credentials, if necessary, the vpn shell script is invoked accordingly using the Popen object from the subprocess module.
This is (roughly) the python script implementing the vpn gnome panel applet:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import gnomeapplet
import sys
import gobject
import subprocess
import os
import time
import logging
class VPN_Applet(gnomeapplet.Applet):
applet = None
image_file = "/usr/local/share/vpn/applet/images/vpn.png"
image_file_hover = "/usr/local/share/vpn/applet/images/vpn_hover.png"
proc_file = "/var/lock/subsys/VPN"
username = ""
passwd = ""
title = "VPN Applet"
cancel = False
logging.basicConfig(level=logging.DEBUG)
def __init__(self, applet, iid):
logging.debug("__init__")
self.applet = applet
size = self.applet.get_size() - 2
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.image_file, size, size)
image = gtk.Image()
image.set_from_pixbuf(pixbuf)
tooltips = gtk.Tooltips()
tooltips.set_tip(image, "Click to connect to VPN\nStatus: Disconnected")
# Status update timeout (check every 3 seconds)
gobject.timeout_add(3000, self.status_timeout, image, tooltips)
self.applet.add(image)
self.applet.connect("button-press-event", self.button_press)
self.applet.connect("change-size", self.change_size, image)
self.applet.connect("change-background", self.change_background)
self.applet.connect("enter-notify-event", self.enter_notify, image)
self.applet.connect("leave-notify-event", self.leave_notify, image)
self.applet.show_all()
def check_connected(self):
if (os.path.exists(self.proc_file)):
return True
else:
return False
def status_timeout(self, image, tooltips):
logging.debug("status_timeout")
if (self.check_connected()):
txt = "disconnect from"
status = "Connected"
else:
txt = "connect to"
status = "Disconnected"
tooltips.set_tip(image, "Click to " + txt + " VPN\nStatus: " + status)
return True
def do_image(self, image_file, image):
logging.debug("do_image")
size = self.applet.get_size() - 2
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(image_file, size, size)
image.set_from_pixbuf(pixbuf)
def change_size(self, applet, new_size, image):
logging.debug("change_size")
self.do_image(self.image_file, image)
def change_background(self, applet, type, color, pixmap):
logging.debug("change_background")
applet.set_style(None)
applet.modify_style(gtk.RcStyle())
if (type == gnomeapplet.COLOR_BACKGROUND):
applet.modify_bg(gtk.STATE_NORMAL, color)
elif (type == gnomeapplet.PIXMAP_BACKGROUND):
applet.get_style().bg_pixmap[gtk.STATE_NORMAL] = pixmap
def enter_notify(self, applet, event, image):
logging.debug("enter_notify")
self.do_image(self.image_file_hover, image)
def leave_notify(self, applet, event, image):
logging.debug("leave_notify")
self.do_image(self.image_file, image)
def button_press(self, button, event):
logging.debug("button_press")
if (event.button == 1):
logging.debug("create dialog")
self.setup_dialog()
if (not(self.cancel)):
if (not(self.check_connected())):
# Prompt for username/password
# Continue prompting until they are non-empty or user cancels
while ((self.username == "" or self.passwd == "") and not(self.cancel)):
self.username_prompt()
# If the user doesn't want to cancel, complete the connection
if (not(self.cancel)):
logging.debug("gksudo vpn start")
args = "-n '" + self.username + "' -p '" + self.passwd + "'"
proc = subprocess.Popen("gksudo -D '" + self.title + "' \"vpn " + args + " start\"",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True)
self.show_progress_bar(proc)
# Clear username/password here
self.username=""
self.passwd=""
else:
logging.debug("gksudo vpn stop")
proc = subprocess.Popen("gksudo -D \"" + self.title + "\" \"vpn stop\"",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True)
self.show_progress_bar(proc)
def setup_dialog(self):
logging.debug("setup_dialog")
vpn_dialog = gtk.Dialog(title=self.title,
flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR),
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
# Test here for vpn connection status and prompt accordingly
if (self.check_connected()):
txt = "disconnect from"
else:
txt = "connect to"
vpn_dialog.connect("response", self.dialog_response)
dialog_label = gtk.Label("Would you like to " + txt + " the Virtual Private Network?")
dialog_label.set_line_wrap(True)
vpn_dialog.vbox.pack_start(dialog_label, padding=10)
vpn_dialog.show_all()
vpn_dialog.run()
def dialog_response(self, dialog, response_id):
logging.debug("dialog_response")
dialog.destroy()
if (response_id == gtk.RESPONSE_ACCEPT):
self.cancel = False
else:
self.cancel = True
def show_progress_bar(self, proc):
logging.debug("show_progress_bar")
if (self.check_connected()):
txt = "Disconnecting from "
else:
txt = "Connecting to "
progress_dialog = gtk.Dialog(title=self.title,
flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR))
progress_dialog.set_deletable(False)
label = gtk.Label(txt + "VPN")
progress_dialog.vbox.pack_start(label, padding=10)
progress_bar = gtk.ProgressBar()
progress_bar.set_pulse_step(0.1)
progress_dialog.vbox.pack_start(progress_bar, padding=10)
gobject.timeout_add(250, self.progress_bar_timeout, progress_dialog,
proc, progress_bar, label)
progress_dialog.show_all()
progress_dialog.run()
def progress_bar_timeout(self, progress_dialog, proc, progress_bar, label):
logging.debug("progress_bar_timeout")
progress_bar.pulse()
if (proc.poll() != None):
if (self.check_connected()):
txt = "Connected to "
else:
txt = "Disconnected from "
label.set_text(txt + "VPN. Click the 'OK' button below to continue.")
label.set_line_wrap(True)
progress_dialog.vbox.remove(progress_bar)
scrolled_window = gtk.ScrolledWindow()
text_view = gtk.TextView()
text_view.set_editable(False)
text_view.set_cursor_visible(False)
text_view.set_size_request(400, 200)
buffer = text_view.get_buffer()
buffer.set_text(proc.stdout.read())
scrolled_window.add(text_view)
progress_dialog.vbox.pack_start(scrolled_window)
progress_dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
progress_dialog.connect("response", self.dialog_response)
progress_dialog.show_all()
return False
else:
return True
def username_changed(self, username):
logging.debug("username_changed")
self.username = username.get_text()
def passwd_changed(self, passwd):
logging.debug("passwd_changed")
self.passwd = passwd.get_text()
def username_prompt_activate(self, entry, dialog):
logging.debug("username_prompt_activate")
dialog.response(gtk.RESPONSE_ACCEPT)
def username_prompt(self):
logging.debug("username_prompt")
username_prompt = gtk.Dialog(title=self.title,
flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR),
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
username = gtk.Entry(32)
username.connect("changed", self.username_changed)
username.connect("activate", self.username_prompt_activate, username_prompt)
passwd = gtk.Entry(32)
passwd.set_visibility(False)
passwd.connect("changed", self.passwd_changed)
passwd.connect("activate", self.username_prompt_activate, username_prompt)
prompt = gtk.Label("Please enter your username and password:")
prompt.set_line_wrap(True)
table = gtk.Table(3, 2)
table.attach(prompt, 0, 2, 0, 1, xpadding=5, ypadding=5)
username_label = gtk.Label("Username (required):")
table.attach(username_label, 0, 1, 1, 2, xpadding=5, ypadding=5)
table.attach(username, 1, 2, 1, 2, xpadding=5, ypadding=5)
passwd_label = gtk.Label("Password (required):")
table.attach(passwd_label, 0, 1, 2, 3, xpadding=5, ypadding=5)
table.attach(passwd, 1, 2, 2, 3, xpadding=5, ypadding=5)
username_prompt.vbox.pack_start(table)
username_prompt.connect("response", self.dialog_response)
username_prompt.set_deletable(False)
username_prompt.show_all()
username_prompt.run()
gobject.type_register(VPN_Applet)
def vpn_factory(applet, iid):
VPN_Applet(applet, iid)
return gtk.TRUE
# run it in a gtk window
if len(sys.argv) > 1 and sys.argv[1] == "run-in-window":
main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
main_window.set_title("VPN Applet")
main_window.connect("destroy", gtk.mainquit)
main_window.set_default_size(36, 36)
app = gnomeapplet.Applet()
vpn_factory(app, None)
app.reparent(main_window)
main_window.show_all()
gtk.main()
sys.exit()
if __name__ == '__main__':
gnomeapplet.bonobo_factory("OAFIID:GNOME_VPN_Applet_Factory",
VPN_Applet.__gtype__,
"VPN Applet", "0.5", vpn_factory)
While there is plenty of Python and PyGTK documentation and examples, there is very little documentation on developing gnome applets using Python. Hopefully the code above will be helpful, in addition to the following links:
GNOME Documentation Library – PanelApplet
PyGTK 2.0 Reference Manual
PyGTK 2.0 Tutorial
Gnome applets with Python
Python Tutorial
subprocess – Subprocess Management


