Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

GNOME Panel Applet SSH Menu

by grantm (Parson)
on Jun 07, 2002 at 14:26 UTC ( [id://172532]=sourcecode: print w/replies, xml ) Need Help??
Category: GUI Programming
Author/Contact Info Grant McLean <grantm@cpan.org>
Description: Update: I have since re-implemented this functionality (with configuration dialogs) using Gtk and Ruby. It's available here.

Warning: Don't use this script as an example of how to write Gtk GUI code in Perl - it uses the old 'Gtk' API (which was current in 2002 when I wrote the script) not the more modern Gtk2 API.

This script scratches a particular itch of mine - I'm administering multiple hosts and regularly want to open a new SSH session to a particular host in a new window. This script provides the list of hosts as a drop-down menu.

It's my first attempt at a GNOME panel applet so I'm interested in feedback from Perl-Gtk gurus who spot unduly complex bits. I'm posting this because I didn't find much in the way of sample code on the web.

I'm running RedHat 7.3 and Ximian GNOME so if it doesn't work for you, you may need to upgrade to the latest versions. The only module I needed over and above RH+Ximian was XML::Simple, but I had that already :-)

Ultimately, I want to implement a configuration dialog but it's functional now so here goes ...

#!/usr/bin/perl -w
######################################################################
+########
# $Id: sshmenu_applet,v 1.10 2002/06/07 14:21:37 grantm Exp $
#
# Title:    sshmenu
#
# Author:   Grant McLean <grantm@cpan.org>
#
# Description:
#
# A GNOME panel applet which provides a pull-down list of hosts.  When
+ a host
# is selected, an SSH session to that host is spawned in a new termina
+l window.
# 

use strict;



######################################################################
+########
# SSHMenu - OO Package which implements this application
#

package SSHMenu;

use strict;

use Gtk::lazy;
use Gnome::Applet;

use XML::Simple;

set_locale Gtk;

use constant TRUE  => 1;
use constant FALSE => 0;

use constant APPLET_NAME => 'sshmenu_applet';


######################################################################
+########
# Constructor: new()
#

sub new {
  my $class = shift;

  my $self = { @_ };

  unless($self->{ConfigFile}) {
    $self->{ConfigFile} = $ENV{HOME} . '/.sshmenu';
  }
  bless($self, $class);

  $self->read_config();

  return($self);
}


######################################################################
+########
# Method: event_loop()
#
# Performs initialisation and enters event loop, never returns.  
#

sub event_loop {
  my $self = shift;

  #$self->add_key();

  init Gnome::AppletWidget APPLET_NAME;

  $self->{applet} = new Gnome::AppletWidget APPLET_NAME;
  $self->{applet}->realize;

  my $button = new Gtk::Button("SSH");
  $button->signal_connect('event', sub { $self->button_press(@_); });
  $self->{applet}->add($button);
  $self->{applet}->show_all();

  Gtk::Tooltips->new()->set_tip(
    $button, $self->{config}->{globals}->{tooltip}, FALSE
  );

  $self->build_menu();

  gtk_main Gnome::AppletWidget;
}


######################################################################
+########
# Method: build_menu()
#
# Creates and populates a Gtk menu widget from contents of config file
+.
# 

sub build_menu {
  my $self = shift;


  $self->{menu} = undef; # delete old menu if there was one

  my $menu = new Gtk::Menu();

  foreach my $host (@{$self->{config}->{host}}) {
    my $menu_item = new Gtk::MenuItem($host->{title});
    $menu->append($menu_item);
    $menu_item->signal_connect('activate', sub { open_win($self, $host
+) });
    $menu_item->show();
  }

  # Add a menu separator followed by some standard menu items

  my $menu_item = new Gtk::MenuItem();
  $menu->append($menu_item);
  $menu_item->show();

  $menu_item = new Gtk::MenuItem('Edit host list');
  $menu->append($menu_item);
  $menu_item->signal_connect('activate', 
    sub { system("gvim $self->{ConfigFile}"); } 
  );
  $menu_item->show();

  $menu_item = new Gtk::MenuItem('Add SSH key to Agent');
  $menu->append($menu_item);
  $menu_item->signal_connect('activate', sub { $self->add_key(1) } );
  $menu_item->show();

  $menu_item = new Gtk::MenuItem('Remove SSH keys from Agent');
  $menu->append($menu_item);
  $menu_item->signal_connect('activate', sub { $self->remove_keys() } 
+);
  $menu_item->show();

  $self->{menu} = $menu;

}


######################################################################
+########
# Method: read_config()
# 
# Looks for XML format config file called $HOME/.sshmenu and slurps th
+e config
# data into $self->{config}.  This routine is called once at startup a
+nd then 
# again before the menu is displayed, to check whether the config file
+ has 
# changed.  If the file has not changed, returns false.
# If the config file is read successfully, return true.  If there is a
+n error
# in the config file, XML::Simple will call die and we don't trap that
+ (yet).
# A default config will be created if it does not exist.
# 

sub read_config {
  my $self = shift;

  unless(-e $self->{ConfigFile}) {
    $self->default_config();
    $self->save_config();
    return;
  }
  my $curr_timestamp = (stat($self->{ConfigFile}))[9];
  if($self->{config_timestamp}) {
    return if($curr_timestamp == $self->{config_timestamp});
  }

  $self->{config} = XMLin(
    $self->{ConfigFile}, 
    forcearray    => [ 'host' ],
    keyattr       => [],
    suppressempty => '',
  );

  $self->{config}->{globals}->{tooltip} ||=
    'Open an SSH session in a new window';

  $self->{config_timestamp} = $curr_timestamp;
}


######################################################################
+########
# Method: default_config()
# 
# Sets up default configuration settings in $self->{config}.
# 

sub default_config {
  my $self  = shift;

  $self->{config} = {
    host => [
      {
    title     => 'localhost',
    sshparams => '127.0.0.1',
    winparams => ''
      }
    ]
  }
}


######################################################################
+########
# Method: save_config()
# 
# Writes out the contents of $self->{config} to $HOME/.sshmenu
# 

sub save_config {
  my $self  = shift;

  XMLout(
    $self->{config},
    rootname   => 'config',
    outputfile => $self->{ConfigFile},
    noattr     => 1,
    xmldecl    => 1,
  );
}


######################################################################
+########
# Method: add_key()
# 
# Adds the default SSH key to the agent.  Remembers whether a key has 
+been
# added.
# 

sub add_key {
  my $self  = shift;
  my $force = shift;


  unless($force) {
    return if($self->{have_key});

    my $keylist = `ssh-add -l`;
    if($keylist =~ m{.ssh/id}) {
      $self->{have_key} = 1;
      return;
    }
  }

  $ENV{SSH_ASKPASS} = '/usr/libexec/openssh/x11-ssh-askpass';
  system("ssh-add </dev/null >/dev/null 2>&1");
}


######################################################################
+########
# Method: remove_keys()
# 
# Removes all keys from SSH agent.
# 

sub remove_keys {
  my $self  = shift;

  system("ssh-add -D </dev/null >/dev/null 2>&1");
}


######################################################################
+########
# Method: button_press()
# 
# Display menu in response to a button-press event.
#

sub button_press {
  my ($self, $button, $event) = @_;

  return(FALSE) if(!$event->{type}  or  $event->{type} ne 'button_pres
+s');
  return(FALSE) if($event->{button} != 1);

  if($self->read_config()) {
    $self->build_menu();
  }
  $self->{menu}->popup(undef, undef, $event->{button}, 0, undef);
  return (TRUE);

}


######################################################################
+########
# Method: open_win()
# 
# Spawn an SSH session in a new shell window.
#

sub open_win {
  my $self = shift;
  my $host = shift;

  $self->add_key();

  my $command =
       "gnome-terminal --use-factory --start-factory-server " .
       "--title='$host->{title}' $host->{winparams} " .
       "--command='ssh $host->{sshparams}'";

  system("$command &");
}


######################################################################
+########
# Main script - instantiates the application object.
# 

package main;

my $app = new SSHMenu;

$app->event_loop();

exit;
Replies are listed 'Best First'.
Re: GNOME Panel Applet SSH Menu
by hossman (Prior) on Jun 07, 2002 at 22:17 UTC
    This may be a stupid question, but why did you need to write a special applet? Why not not just create a new menu using the gnome "menu editor" and bind it to a button?
      No, that is a good question. I wanted one button on the panel that gave me a list of hosts. The panel would only let me add a button for a top-level menu and the menu editor would not allow me to create a new top-level menu (perhaps because I'm not root?). I could have done it using the favorites menu but it would have been at least one menu down.

      Of course another (possibly more accurate) answer is that I enjoy re-inventing wheels in Perl:-) I also wanted to take Perl-Gtk for a spin. The result is something that fits my requirement better than a generic menu would have. And it allows me to edit the menu with gvim - the one true editor.

Re: GNOME Panel Applet SSH Menu
by jepri (Parson) on Jun 08, 2002 at 09:22 UTC
    Great program. I've been meaning to write something similar for ages. I know exactly how you feel. I've got, like six servers and wish for this constantly. It'll have a permanent place in my gnome-panel, for sure.

    Update: Oh yeah, works fine on Debian testing. Just install libgnome-applet-perl. i'll let you know about Solaris on Monday.

    ____________________
    Jeremy
    I didn't believe in evil until I dated it.

Re: GNOME Panel Applet SSH Menu
by Anonymous Monk on Apr 19, 2007 at 09:12 UTC
    I love it, I'm an admin for over a hundred *nix servers in multiple locations around the world and this works a treat! I can view all the servers and don't have to remember every hostname, cheers!!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://172532]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (7)
As of 2024-03-28 19:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found