Posts Tagged ‘gnome’

VPN Gnome Panel Applet
Thursday, May 15th, 2008

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

Simplifying Synergy in Gnome
Wednesday, April 9th, 2008

Synergy has worked well for me over the last couple days, but setting up the connection (although not difficult) required a few steps that I’ve tried to streamline.

The following setup is based on a synergy configuration similar to the following.:


section: screens
    desktop:
    laptop:
end
section: links
    desktop:
        right = laptop
    laptop:
        left = desktop
end

BTW, desktop and laptop are arbitrary names I’ve selected for clarity.

Automatically start the synergy server after logging in to gnome on the “desktop” computer to which the mouse/keyboard is physically connected as follows:

  • Go to Main Menu -> System -> Preferences -> Sessions
  • Under Startup Programs click Add
  • In the Edit Startup Program dialog enter the following command to start the synergy server: synergys -n desktop

Since synergy traffic is plaintext, I forward the communication through ssh from my laptop as described in the Synergy documentation. I also use ssh to communicate with my desktop computer for other purposes, so I use public key authentication to avoid having to type my remote password every time I try to connect. Since I use a passphrase, however, I have to add my key to the ssh agent with a call to ssh-add. This requires me to enter my passphrase…but once added I can connect to the desktop machine without a passphrase for the duration of my session. For more info, check out the Ubuntu SSHHowTo documentation. Since I tend to forget to add the key, I decided to run ssh-add when I log into gnome on my laptop (”laptop” above) as follows:

  • Go to Main Menu -> System -> Preferences -> Sessions
  • Under Startup Programs click Add
  • In the Edit Startup Program dialog enter the following to add your key to the authentication agent: ssh-add

In order to establish the synergy client connection, I wrote the following script:


#!/bin/bash
#
# Script to securely connect to synergy
# server on desktop machine for sharing
# keyboard and mouse
#

# IP address of desktop machine
DT_IP="xxx.xxx.xxx.xxx"

# Setup port forwarding
ssh -o ExitOnForwardFailure=true -f -N -L 24800:$DT_IP:24800 $DT_IP

# Start synergy client
killall 'synergyc'
synergyc -1 -n laptop localhost

This script establishes the port forwarding through ssh and then starts the synergy client. Instead of calling the script from the command line, I added a custom application launcher to a panel in order to start the connection with a simple mouse (touchpad) click. There are a few things to note:

  • The ssh connection doesn’t require a passphrase here since I added the key to the authorization agent using ssh-add at gnome startup
  • The ExitOnForwardFailure option is used so that the process will exit if the connection fails. If, for any reason, the synergy client connection is closed, the ssh forwarding will still remain active. This script can be rerun without any worry of multiple (failed) ssh forwarding attempts.
  • The -1 option is used in the synergy client so that it won’t try to restart if the connection fails for any reason. Without this option, the client will continuously attempt to reconnect, consuming system resources.

So now, when I log into gnome on the desktop computer the synergy server automatically starts. When I log into gnome on the laptop machine I am prompted for my ssh passphrase. All I need to do to start a secure synergy connection from my laptop to my desktop is click on my custom application launcher.

(GNOME) Evolution extremely slow
Saturday, December 8th, 2007

I have evolution 2.12.1 installed on Ubuntu 7.10 (Gutsy) along with the evolution-exchange plugin on my home desktop and laptop. For some time now, I’ve been unable to figure out why I can connect to exchange with no problems from my desktop, but not from my laptop. All operations using evolution on my laptop would take several minutes, or sometimes it seemed like everything would grind to a halt, leaving evolution completely unusable. I spent some time last night trying to get to the bottom of things, and stumbled across a thread that helped me make some progress. Thanks to joshmachine’s suggestion to start evolution with the E2K_DEBUG environment variable, something like

$ export E2K_DEBUG=4; evolution

I could view some exchange connector debugging output to figure out that evolution was timing out when trying to connect to the global catalog server I had configured. It turns out that this was the one setting these machines didn’t have in common! When I cleared the setting, everything worked flawlessly…well, almost. Now I have to figure out what’s causing the connection to the directory server to fail.

Something to add to the never ending list of things to do:
- Figure out why there isn’t a command line option in evolution to enable or increase the verbosity of debugging output for evolution and it’s plugins.

Powered by Laughing Squid