#!/usr/bin/perl
#
# Copyright 2006-2010 SPARTA, Inc.  All rights reserved.  See the COPYING
# file distributed with this software for details.
#
# DNSSEC-Tools:  blinkenlights
#
#	blinkenlights is a display tool for use with the DNSSEC-Tools rollerd
#	program.  As rollerd rolls the zones listed in the rollrec file,
#	blinkenlights will display status information for the file's zones.
#
#	blinkenlights is implemented in Perl/Tk, so both Perl and Perl/Tk must
#	be installed on your system.
#

use strict;

use Getopt::Long qw(:config no_ignore_case_always);

use Net::DNS::SEC::Tools::dnssectools;
use Net::DNS::SEC::Tools::conf;
use Net::DNS::SEC::Tools::keyrec;
use Net::DNS::SEC::Tools::rollrec;
use Net::DNS::SEC::Tools::timetrans;
use Net::DNS::SEC::Tools::BootStrap;


######################################################################
#
# Detect required Perl modules.
#
dnssec_tools_load_mods(
			'Tk'		=> "",
			'Tk::Dialog'	=> "",
			'Tk::Pane'	=> "",
			'Tk::Table'	=> "",
		      );

#
# Version information.
#
my $NAME   = "blinkenlights";
my $VERS   = "$NAME version: 1.2";
my $DTVERS = "DNSSEC-Tools Version: 1.7";

#######################################################################
#
# Data required for command line options.
#
my %options = ();			# Filled option array.
my @opts =
(
	"Version",			# Display the version number.
	"display",			# Tell rollerd to execute us.
	"help",				# Give a usage message and exit.
);

my $version	= 0;			# Display the version number.

#######################################################################

#
# blinkenlights' configuration file.
#
my $CONFFILE	= "blinkenlights.conf";
my $BLCONFIG	= "./rc.blinkenlights";

#######################################################################
#
# Data involved with column layout.
#

#
# Row constants.
#
my $INFOROW  = 0;		# Row on which columns titles will be shown.
my $TITLEROW = 1;		# Row on which columns titles will be shown.
my $STARTROW = 2;		# First row on which zone info will be shown.
my $ROWINCR  = 3;		# Number of rows for each zone.

#
# Column constants.
#
my $COL0  = 0;			# Column 0.
my $COL1  = 1;			# Column 1.
my $COL2  = 2;			# Column 2.
my $COL3  = 3;			# Column 3.
my $COL4  = 4;			# Column 4.
my $COL5  = 5;			# Column 5.
my $COL6  = 6;			# Column 6.
my $MAXCOLS = 7;		# Maximum number of columns.

#
# Column constants for infostripe.
#
my $RRFCOL	= $COL0;	# Rollrec filename.
my $INFO1	= $COL1;	# Unused.
my $ROLLCNTCOL	= $COL2;	# Rolled zones count.
my $SKIPCNTCOL	= $COL3;	# Skipped zones count.
my $SLEEPCOL	= $COL4;	# Rollerd's sleeptime.
my $INFO5	= $COL5;	# Unused.
my $INFO6	= $COL6;	# Unused.

#
# Default column constants for zonestripes.
#
my $NAMECOL	= $COL0;	# Rollrec filename. 
my $ZONECOL	= $COL1;	# Zone name. 
my $PHASECOL	= $COL2;	# Rollover phase.
my $ZSKLBLCOL	= $COL3;	# ZSK label.
my $ZSKKEYCOL	= $COL4;	# ZSK key/signing set name.
my $KSKLBLCOL	= $COL5;	# KSK label.
my $KSKKEYCOL	= $COL6;	# KSK key/signing set name.

my $UNUSED	= -1;		# Unused column.

#
# Variables holding current column layout data.
#
my $phasecol	= $PHASECOL;	# Rollover phase.
my $zskkeycol	= $ZSKKEYCOL;	# Current ZSK key/signing set name.
my $zsklblcol	= $ZSKLBLCOL;	# Current ZSK label.
my $kskkeycol	= $KSKKEYCOL;	# Current KSK key/signing set name.
my $ksklblcol	= $KSKLBLCOL;	# Current KSK label.

my $showksksets	= 1;		# Flag indicating if KSK signing sets are shown.
my $showzsksets	= 1;		# Flag indicating if ZSK signing sets are shown.

my $numcols	= $MAXCOLS;	# Number of columns in use.

#######################################################################
#
# Data involved with display.
#

my $PAINTMAX	= 100;		# Maximum screen paints before screen rebuild.
my $paintcount	= -1;		# Count of screen paints.

my $DLGHEIGHT = 10;		# Height of the dialog box.

#
# Font size for output window.
#
my $FONTMIN	= 10;		# Minimum font size we'll allow.
my $FONTMAX	= 42;		# Maximum font size we'll allow.
my $FONTINCR	= 2;		# Font creation loop increment.
my $fontsize	= 18;
my $font	= "*-*-bold-r-*-*-$fontsize-*-*-*-*-*-*-*";

#
# Foreground colors for zones.
#
my $NORMALFG	= 'black';			# Normal foreground color.
my $SELECTEDFG	= 'white';			# Selected foreground color.

#
# Background colors for the zones, cycling through the list for table rows.
#
my $NUMCOLORS = 3;				# Maximum number of colors.
my $INFOBG    = "white";			# Color for the info row.

my $colorind = 0;				# Current color index.
my $skipcolor = "grey";				# Color for skip zones.
my $ERRORBG   = "yellow";			# Color for error rows.

#
# The @kphasecolors and @zphasecolors arrays hold the color names are used for
# the background of the rollover-status column.  (If you can't immediately
# figure out which array is used for what, you shouldn't be editing this file.)
# The first color is the base color for normal operations.  The other colors are
# used for the rollover phases.  They should be shades of the base color, and
# they should go from a light shade to progressively darker shades.  The base
# color should be the darkest.
#
# The color names are taken from the X11 rgb.txt file (X11 1.1.3 - XFree86
# 4.4.0 for MacOS X.)  If these aren't available in your rgb.txt file, you
# should select similar names.  The actual values used are given in the 
# blinkenlights pod, in case you need to find something similar.
#

my @kphasecolors =
(
	[
		'blue',			# phase 0
		'lightblue2',		# phase 1
		'darkslategray1',	# phase 2
		'skyblue1',		# phase 3
		'steelblue1',		# phase 4
		'turquoise1',		# phase 5
		'cornflower blue',	# phase 6
		'dodger blue',		# phase 7
	],

	[
		'red',			# phase 0
		'pink',			# phase 1
		'lightsalmon1',		# phase 2
		'tomato',		# phase 3
		'indianred',		# phase 4
		'violetred1',		# phase 5
		'orangered1',		# phase 6
		'firebrick1',		# phase 7
	],

	[
		'green',		# phase 0
		'darkseagreen1',	# phase 1
		'darkolivegreen1',	# phase 2
		'lightgreen',		# phase 3
		'seagreen1',		# phase 4
		'spring green',		# phase 5
		'greenyellow',		# phase 6
		'lawngreen'		# phase 7
	],

);

my @zphasecolors =
(
	[
		'blue',			# phase 0
		'LightBlue2',		# phase 1
		'skyblue1',		# phase 2
		'cornflower blue',	# phase 3
		'dodger blue'		# phase 4
	],

	[
		'red',			# phase 0
		'pink',			# phase 1
		'indianred',		# phase 2
		'violetred1',		# phase 3
		'orangered1'		# phase 4
	],

	[
		'green',		# phase 0
		'lightgreen',		# phase 1
		'seagreen1',		# phase 2
		'greenyellow',		# phase 3
		'lawngreen'		# phase 4
	], 
);

#
# Descriptions of the KSK rollover phases.
#
my @kphasedescr =
(
	"  normal operation  ",				# phase 0
	"  first cache-expire wait  ",			# phase 1
	"  sign with KSKCUR, KSKPUB, and ZSKCUR  ",	# phase 2
	"  second cache-expire wait  ",			# phase 3
	"  sign with KSKPUB and ZSKCUR  ",		# phase 4
	"  transfer keyset to parent  ",		# phase 5
	"  wait for parent to publish DS  ",		# phase 6
	"  reload zone  ",				# phase 7
);

#
# Descriptions of the ZSK rollover phases.
#
my @zphasedescr =
(
	"  normal operation  ",				# phase 0
	"  first cache-expire wait  ",			# phase 1
	"  sign with ZSKCUR and ZSKPUB  ",		# phase 2
	"  second cache-expire wait  ",			# phase 3
	"  sign with ZSKPUB and ZSKNEW  ",		# phase 4
);

#######################################################################
#
# Global Tk widgets.
#

#
# The main window and its frames.
#
my $wm;							# Main window.
my $mbar;						# Menubar frame.
my $helpwin;						# Help window.
my $body;						# Window body frame.
my $zonetab;						# Zone data table.
my $null;						# Empty frame.

my $file;						# File menu.
my $disp;						# Edit menu.
my $cmdg;						# General Commands menu.
my $cmdz;						# ZSK Commands menu.
my $cmdk;						# KSK Commands menu.
my $zdsp;						# Display menu.
my $help;						# Help menu.

#
# Menu item widgets.
#
my $op_clrs;						# Colors item.
my $op_mdfy;						# Modify toggle.
my $op_shad;						# Shading toggle.
my $op_skip;						# Skip toggle.
my $zd_ksk;						# KSK set display toggle
my $zd_zsk;						# ZSK set display toggle

#
# Messages for the Display menu.
#
my $ALLOWCMDS	 = "Allow Commands";
my $DISALLOWCMDS = "Disallow Commands";

my $COLORING	 = "Color Zone Stripes";
my $NOCOLORING	 = "Don't Color Zone Stripes";

my $SHADING	 = "Shade Status Column";
my $NOSHADING	 = "Don't Shade Status Column";

my $SHOWSKIP	 = "Display Skipped Zones";
my $NOSKIP	 = "Hide Skipped Zones";

#
# Messages for the Zone menu.
#
my $NOKSKSETS = 'Hide KSK Sets';
my $KSKSETS   = 'Show KSK Sets';
my $NOZSKSETS = 'Hide ZSK Sets';
my $ZSKSETS   = 'Show ZSK Sets';


#
# Flags for the menu options.
#
my $nocolors;				# Use/don't use colors in stripes.
my $nomodify;				# Allowing/disallowing commands flag.
my $noshading;				# Shading/not shading zone stripes flag.
my $noskip;				# Show/don't show skipped zones flag.

my $shademsg;				# Message about shading.
my $ksksetmsg;				# Message about KSK signing set display.
my $zsksetmsg;				# Message about ZSK signing set display.

#######################################################################
#
# Global shtuff.
#

#
# Flags.
#
my $inhelpwind = 0;				# Flag for showing help window.

#
# Filename variables.
#
my $rrfile = "dummy";				# Rollrec file being watched.
my $title = "dummy";				# File node for title.

#
# Zone information.
#
my %rollers = ();				# Zones that might roll.
my %zones = ();					# Zones we're watching.
my %zonecolors = ();				# Zone color indices.
my %zonerows = ();				# Zones' starting-row indices.
my @zonenames = ();				# Screen-order list of zones.
my %zonenames = ();				# Rollrec name -> zone name map.
my $zonecnt = 1;				# Count of zones we're watching.
my %keyrecs = ();				# Zones' keyrec filenames.
my %display = ();				# Zones' display flag.

my %badzones = ();				# Zones with problems.

#
# Saved zone data, needed for repaints.
#
my %zonechron = ();				# Zones' chronoses.
my %zonephase = ();				# Zones' phases.
my %zoneroll  = ();				# Zones' rollover types.

#
# Data for the info stripe.
#
my $rollcntmsg = ' ';				# Count of rolled zones.
my $skipcntmsg = ' ';				# Count of skipped zones.

#
# Data for button-selected zones.
#
my $selzone;					# Selected zone name.
my $selwidget;					# Selected zone's widget.
my $lastrow = $STARTROW;			# Final row index.

#
# Command paths.
#
my $rollctl;					# Path for rollctl.

###########################################################################

main();
exit(0);

#---------------------------------------------------------------------------
# Routine:	main()
#
# Purpose:	Do shtuff.
#
sub main
{
	erraction(ERR_EXIT);

	$| = 1;

	#
	# Check for options.
	#
	optsandargs();

	#
	# Read our configuration file.
	#
	readconfig();

	#
	# Ensure that rollerd is actually running.
	#
	rollerdup();

	#
	# Build the main window.
	#
	buildmainwind();

	#
	# Start the whole shebang rollin'.
	#
	MainLoop();
}

#---------------------------------------------------------------------------
# Routine:	optsandargs()
#
# Purpose:	Parse the command line for options and arguments.
#
sub optsandargs
{
	my $argc = @ARGV;		# Command line argument count.

	my %blconf;			# DNSSEC-Tools config file contents.
	my $confdir;			# DNSSEC-Tools configuration directory.
	my $conffile;			# blinkenlights configuration file.

	#
	# Parse the options.
	#
	GetOptions(\%options,@opts) || usage();

	#
	# Show the version number if requested
	#
	version() if(defined($options{'Version'}));

	#
	# Give a usage flag if asked.
	#
	usage() if(defined($options{'help'}));

	#
	# Get the path for the rollctl command.
	#
	if(($rollctl=dt_cmdpath('rollctl')) eq '')
	{
		print STDERR "no absolute path defined for rollctl; exiting...\n";
		exit(1);
	}

	#
	# Kick rollerd into *really* starting blinkenlights, but only if
	# no arguments were given or -display was given.
	#
	if(($argc == 0) || defined($options{'display'}))
	{
		exec "$rollctl -q -display" || print STDERR "unable to execute $rollctl -display\n";
		exit(1);
	}

	#
	# Save the name of the rollrec file.
	#
	$rrfile = $ARGV[0];

	#
	# Build the blinkenlights configuration file's name.
	#
	$confdir = getconfdir();
	$conffile = "$confdir/$CONFFILE";

	#
	# Set the option values based on the config file.
	# Non-blinkenlights options will be ignored by setopt().
	#
	%blconf = parseconfig($conffile);
	foreach my $dtk (keys(%blconf))
	{
		setopt($dtk,$blconf{$dtk});
	}

}

#---------------------------------------------------------------------------
# Routine:	buildmainwind()
#
# Purpose:	Build the main window.
#
sub buildmainwind
{
	my $curfile;					# Current keyrec.
	my $nulline;					# Empty line.

	my $colormsg;					# Color menu message.
	my $modmsg;					# Modify menu message.
	my $skipmsg;					# Skip menu message.
	my $fnsmenu;					# Font-size menu.

	#
	# Create the main window.
	#
	$wm = MainWindow->new(-title => "blinkenlights");

	#
	# Create the frames we'll need.
	#
	$mbar = $wm->Frame(-relief => 'raised', -borderwidth => 1);
	$body = $wm->Frame(-relief => 'raised', -borderwidth => 1);
	$null = $wm->Frame(-relief => 'raised', -borderwidth => 1);

	$mbar->pack(-anchor => 'n', -side => 'top', -fill => 'x', -expand => 1);
	$body->pack(-anchor => 'n', -side => 'top', -fill => 'x', -expand => 1);
	$null->pack(-expand => 1,   -fill => 'x');

	#
	# Create our menus.
	#
	$file = $mbar->Menubutton(-text	     => 'File',
				  -tearoff   => 0,
				  -underline => 0);
	$disp = $mbar->Menubutton(-text	     => 'Options',
				  -tearoff   => 0,
				  -underline => 0);
	$cmdg = $mbar->Menubutton(-text	     => 'General Control',
				  -tearoff   => 1,
				  -underline => 0);
	$cmdz = $mbar->Menubutton(-text	     => 'ZSK Control',
				  -tearoff   => 1,
				  -underline => 0);
	$cmdk = $mbar->Menubutton(-text	     => 'KSK Control',
				  -tearoff   => 1,
				  -underline => 0);
	$zdsp = $mbar->Menubutton(-text	     => 'Zone Display',
				  -tearoff   => 1,
				  -underline => 5);
	$help = $mbar->Menubutton(-text      => 'Help',
				  -tearoff   => 0,
				  -underline => 0);
	$mbar->pack(-side => 'top', -fill => 'x');

	##################################################
	#
	# Add the File menu entries.
	#

	$file->command(-label => 'Halt Rollerd',
		       -command => [\&commander, "halt"]);
	$file->separator();
	$file->command(-label => 'Quit',
		       -command => \&file_quit,
		       -accelerator => 'Ctrl+Q',
		       -underline => 0);
	$file->pack(-side => 'left');

	$wm->bind('<Control-Key-Q>',\&file_quit);
	$wm->bind('<Control-Key-q>',\&file_quit);

	##################################################
	#
	# Add the Options menu entries.
	#
	$colormsg = $NOCOLORING;
	$colormsg = $COLORING if($nocolors);
	$op_clrs = $disp->command(-label => $colormsg,-command => \&disp_color);
	$disp->pack(-side => 'left');

	$shademsg = $NOSHADING;
	$shademsg = $SHADING if($noshading);
	$op_shad = $disp->command(-label => $shademsg,-command => \&disp_shade);
	$disp->pack(-side => 'left');

	$skipmsg = $NOSKIP;
	$skipmsg = $SHOWSKIP if($noskip);
	$op_skip = $disp->command(-label => $skipmsg, -command => \&disp_skip);
	$disp->pack(-side => 'left');

	$disp->separator();
	$disp->pack(-side => 'left');

	$modmsg = $DISALLOWCMDS;
	$modmsg = $ALLOWCMDS if($nomodify);
	$op_mdfy = $disp->command(-label => $modmsg, -command => \&disp_modify);
	$disp->pack(-side => 'left');

	$disp->separator();
	$disp->pack(-side => 'left');

	#
	# Add a bunch of font sizes.
	#
	$fnsmenu = $disp->cascade(-label => "Font Size");
	for(my $ind = $FONTMIN; $ind <= $FONTMAX; $ind += $FONTINCR)
	{
		$fnsmenu->radiobutton(-label	=> "$ind",
				      -variable => \$fontsize,
				      -command	=> \&disp_font);
	}
	$disp->pack(-side => 'left');

	##################################################
	#
	# Add the General Control menu entries.
	#
	$cmdg->command(-label => 'Run the Queue',
		       -command => [\&general_cmds, "runqueue"]);
	$cmdg->separator();
	$cmdg->command(-label => 'Skip Selected Zone',
		       -command => [\&general_cmds, "skipzone"],
		       -accelerator => 'Ctrl+S');
	$cmdg->command(-label => 'Skip All Zones',
		       -command => [\&general_cmds, "skipall"]);

	$cmdg->pack(-side => 'left');

	if($nomodify)
	{
		$cmdg->configure(-state => 'disabled');
	}

	##################################################
	#
	# Add the ZSK Control menu entries.
	#
	$cmdz->command(-label => "Roll Selected Zone's ZSK",
		       -command => [\&zskcmds, "rollzsk"],
		       -accelerator => 'Ctrl+R');
	$cmdz->command(-label => "Roll All Zones' ZSKs",
		       -command => [\&zskcmds, "rollallzsks"]);

	$cmdz->pack(-side => 'left');

	$wm->bind('<Control-Key-r>', \&zone_rollzsk);
	$wm->bind('<Control-Key-s>', \&zone_skipzone);

	if($nomodify)
	{
		$cmdz->configure(-state => 'disabled');
	}

	##################################################
	#
	# Add the KSK Control menu entries.
	#
	$cmdk->command(-label	=> 'DS Published Selected Zone',
		       -command => [\&kskcmds, "dspub"]);
	$cmdk->command(-label	=> 'DS Published All Zones',
		       -command => [\&kskcmds, "dspuball"], );

	$cmdk->pack(-side => 'left');

	if($nomodify)
	{
		$cmdk->configure(-state => 'disabled');
	}

	##################################################
	#
	# Add the Zone Display menu entries.
	#
	$zdsp->command(-label	=> 'Zone Selection...',
		       -command => \&zdisp_select);
	$zdsp->command(-label	=> 'Display All Zones',
		       -command => [\&zdisp_setter, 1]);
	$zdsp->command(-label	=> 'Hide All Zones',
		       -command => [\&zdisp_setter, 0]);
	$zdsp->separator();
	$disp->pack(-side => 'left');

	$ksksetmsg = $NOKSKSETS;
	$zd_ksk = $zdsp->command(-label   => $ksksetmsg,
				  -command => [\&disp_sets, 'KSK'],
				  -state   => 'normal');

	$zsksetmsg = $NOZSKSETS;
	$zd_zsk = $zdsp->command(-label   => $zsksetmsg,
				  -command => [\&disp_sets, 'ZSK'],
				  -state   => 'normal');
	$zdsp->command(-label   => 'Hide All Keysets',
				  -command => [\&disp_sets, 'HIDE ALL'],
				  -state   => 'normal');
	$zdsp->command(-label   => 'Show All Keysets',
				  -command => [\&disp_sets, 'SHOW ALL'],
				  -state   => 'normal');

	$zdsp->pack(-side => 'left');

	##################################################
	#
	# Add the Help menu entries.
	#
	$help->command(-label => 'Help',
		       -command => \&help_help,
		       -accelerator => 'Ctrl+H',
		       -underline => 0);
	$help->pack(-side => 'right');

	$wm->bind('<Control-Key-h>',\&help_help);

	##################################################
	#
	# Add the big ol' zone-status widget.
	#
	$zonetab = maketable();
	$zonetab->pack(-fill => 'both', -expand => 1);
	$body->pack(-fill => 'both', -expand => 1);

	#
	# Get the rollrec file info.
	#
	readrrf($rrfile,0);

	#
	# Set up to get input from rollerd.
	#
	$wm->fileevent('STDIN',readable => \&rollerdcmd);

	#
	# Set up to handle mouse clicks.
	#
	$wm->bind('<Button>',\&selector);
}

##############################################################################
#
# Menu widget interface routines.
#
##############################################################################

#---------------------------------------------------------------------------
# Routine:	file_quit()
#
# Purpose:	Handle the quit menu command.
#
sub file_quit
{
	exit(0);
}

#---------------------------------------------------------------------------
# Routine:	disp_color()
#
# Purpose:	Handle the Colors menu toggle.
#
sub disp_color
{
	if($nocolors)
	{
		$op_clrs->configure(-label => $NOCOLORING);
		$cmdg->configure(-state => 'normal');
		$nocolors = 0;
	}
	else
	{
		$op_clrs->configure(-label => $COLORING);
		$cmdg->configure(-state => 'disabled');
		$nocolors = 1;
	}

	calcrows();
	repaint();
}

#---------------------------------------------------------------------------
# Routine:	disp_font()
#
# Purpose:	Handle the Font Size menu item.  We'll set the font string
#		and repaint the screen.
#
sub disp_font
{
	setfont();
	painter($PAINTMAX+1);
}

#---------------------------------------------------------------------------
# Routine:	disp_modify()
#
# Purpose:	Handle the Modify menu toggle.
#
sub disp_modify
{
	if($nomodify)
	{
		$op_mdfy->configure(-label => $DISALLOWCMDS);
		$cmdg->configure(-state => 'normal');
		$cmdz->configure(-state => 'normal');
		$cmdk->configure(-state => 'normal');
		$nomodify = 0;
	}
	else
	{
		$op_mdfy->configure(-label => $ALLOWCMDS);
		$cmdg->configure(-state => 'disabled');
		$cmdz->configure(-state => 'disabled');
		$cmdk->configure(-state => 'disabled');
		$nomodify = 1;
	}
}

#---------------------------------------------------------------------------
# Routine:	disp_shad()
#
# Purpose:	Handle the Shading menu toggle.
#
sub disp_shade
{
	if($noshading)
	{
		$op_shad->configure(-label => $NOSHADING);
		$noshading = 0;
	}
	else
	{
		$op_shad->configure(-label => $SHADING);
		$noshading = 1;
	}

	calcrows();
	repaint();
}

#---------------------------------------------------------------------------
# Routine:	disp_skip()
#
# Purpose:	Handle the Skip menu toggle.
#
sub disp_skip
{
	if($noskip)
	{
		$op_skip->configure(-label => $NOSKIP);
		$noskip = 0;
	}
	else
	{
		$op_skip->configure(-label => $SHOWSKIP);
		$noskip = 1;
	}

	calcrows();
	repaint();
}

#---------------------------------------------------------------------------
# Routine:	disp_sets()
#
# Purpose:	Handle the KSK/ZSK signing sets display menu toggle.
#		When handling a menu execution, the following actions are
#		taken, regardless of whether it's to show or not-show KSK
#		or ZSK signing sets.  
#
#			- Toggle the menu label.
#			- Flip the appropriate display flag.
#			- The appropriate key and label column variables
#			  will be set.
#			- The display table's column count will be adjusted
#			  by 2.
#
#		The meaning of "appropriate" depends on which of the four
#		possible reasons for calling the routine:  enable KSK display,
#		disable KSK display, enable ZSK display, disable ZSK display.
#
sub disp_sets
{
	my $menucmd = shift;			# Triggering menu command.

	my $lbl;				# New menu label.

	#
	# Handle a KSK enable or disable command.
	#
	if($menucmd eq 'KSK')
	{
		#
		# If we're currently displaying KSK sets, we'll stop.
		# If we aren't currently displaying KSK sets, we'll start.
		#
		if($showksksets)
		{
			$lbl = $KSKSETS;
			$showksksets = 0;
			$numcols -= 2;

			$kskkeycol = $UNUSED;
			$ksklblcol = $UNUSED;
		}
		else
		{
			$lbl = $NOKSKSETS;
			$showksksets = 1;
			$numcols += 2;

			#
			# Assume we're putting the KSKs in their default
			# location.  If ZSKs aren't being displayed, we'll
			# slide the KSKs over into the ZSKs' columns.
			#
			$kskkeycol = $KSKKEYCOL;
			$ksklblcol = $KSKLBLCOL;
			if(!$showzsksets)
			{
				$kskkeycol = $ZSKKEYCOL;
				$ksklblcol = $ZSKLBLCOL;
			}
		}

		#
		# Set the menu item's new label.
		#
		$zd_ksk->configure(-label => $lbl);
	}
	elsif($menucmd eq 'ZSK')
	{
		#
		# If we're currently displaying ZSK sets, we'll stop.
		# If we aren't currently displaying ZSK sets, we'll start.
		#
		if($showzsksets)
		{
			$lbl = $ZSKSETS;
			$showzsksets = 0;
			$numcols -= 2;

			#
			# We're halting display of the ZSKs.  If KSKs are
			# being displayed, we'll slide them over into the
			# ZSKs' normal columns.
			#
			$zskkeycol = $UNUSED;
			$zsklblcol = $UNUSED;
			if($showksksets)
			{
				$kskkeycol = $ZSKKEYCOL;
				$ksklblcol = $ZSKLBLCOL;
			}
		}
		else
		{
			$lbl = $NOZSKSETS;
			$showzsksets = 1;
			$numcols += 2;

			#
			# We're putting the ZSKs in their default location.
			# If KSKs are being displayed, we'll slide them over
			# into their normal columns.
			#
			$zskkeycol = $ZSKKEYCOL;
			$zsklblcol = $ZSKLBLCOL;
			if($showksksets)
			{
				$kskkeycol = $KSKKEYCOL;
				$ksklblcol = $KSKLBLCOL;
			}
		}

		#
		# Set the menu item's new label.
		#
		$zd_zsk->configure(-label => $lbl);
	}
	elsif($menucmd eq 'HIDE ALL')
	{
		#
		# Hide all the signing sets.
		#
		disp_sets('KSK') if($showksksets);
		disp_sets('ZSK') if($showzsksets);
		return;
	}
	elsif($menucmd eq 'SHOW ALL')
	{
		#
		# Hide all the signing sets.
		#
		disp_sets('KSK') if(!$showksksets);
		disp_sets('ZSK') if(!$showzsksets);
		return;
	}

	#
	# Recreate the display table.
	#
	$paintcount = $PAINTMAX + 1;
	painter();

	#
	# Recalculate the number of rows we need and redraw the screen.
	#
	calcrows();
	repaint();
}

#---------------------------------------------------------------------------
# Routine:	zone_rollzsk()
#
# Purpose:	Handle the Roll ZSK menu command.
#
sub zone_rollzsk
{
	return if($rollers{$selzone});
	general_cmds("rollzsk");
}

#---------------------------------------------------------------------------
# Routine:	zone_skipzone()
#
# Purpose:	Handle the Skip Zone menu command.
#
sub zone_skipzone
{
	return if(!$rollers{$selzone});
	general_cmds("skipzone");
}

#---------------------------------------------------------------------------
# Routine:	general_cmds()
#
# Purpose:	Handle the General Commands menu commands.
#
sub general_cmds
{
	my $rcmd = shift;				# User's command.
	my $args = $rcmd;				# Command arguments.
	my $rollcmd;					# Command to execute.
	my $ret;					# Command retcode.

	my $zone = $selzone;				# Zone to handle.

	#
	# Don't allow commands if -nomodify was given.
	#
	return if($nomodify);

	#
	# These commands need a zone.  By hook or by crook, we'll ensure
	# that we have one.
	#
	if($rcmd eq "skipzone")
	{
		#
		# If a zone has been selected, we'll use it.  If not,
		# we'll prompt the user for the zone.
		#
		if($selzone ne '')
		{
			$args = "$rcmd \"$selzone\"";
		}
		else
		{
			$zone = getzone($rcmd);
			return if($zone eq "");
			$args = "$rcmd \"$zone\"";
		}
	}

	#
	# Build and execute the command string.
	#
	$rollcmd = "$rollctl -q -$args";
	$ret = system($rollcmd);

	#
	# If the command failed, we'll re-run the command and let the
	# user see the failure message.
	#
	$ret = $ret >> 8;
	if($ret != 0)
	{
		#
		# Exit if rollerd isn't running.
		#
		if($ret == 255)
		{
			exit(1);
		}

		#
		# Quietly run the command again.
		*
		$rollcmd = "$rollctl -q $args";
		system($rollcmd);

		#
		# If we were trying to skip this zone, we'll mark
		# it as having a problem and repaint its stripe.
		#
		if($rcmd eq "skipzone")
		{
			$badzones{$zone} = 1;
			zonestripe($zone,'KSK',0);
		}
	}
	else
	{
		delete($badzones{$zone});
	}
}

#---------------------------------------------------------------------------
# Routine:	zskcmds()
#
# Purpose:	Handle the ZSK Commands menu commands.
#
sub zskcmds
{
	my $rcmd = shift;				# User's command.
	my $args = $rcmd;				# Command arguments.
	my $rollcmd;					# Command to execute.
	my $ret;					# Command retcode.

	my $zone = $selzone;				# Zone to handle.

	#
	# Don't allow commands if -nomodify was given.
	#
	return if($nomodify);

	#
	# These commands need a zone.  By hook or by crook, we'll ensure
	# that we have one.
	#
	if($rcmd eq "rollzsk")
	{
		#
		# If a zone has been selected, we'll use it.  If not,
		# we'll prompt the user for the zone.
		#
		if($selzone ne '')
		{
			$args = "$rcmd \"$selzone\"";
		}
		else
		{
			$zone = getzone($rcmd);
			return if($zone eq "");
			$args = "$rcmd \"$zone\"";
		}
	}

	#
	# Build and execute the command string.
	#
	$rollcmd = "$rollctl -q -$args";
	$ret = system($rollcmd);

	#
	# If the command failed, we'll re-run the command and let the
	# user see the failure message.
	#
	$ret = $ret >> 8;
	if($ret != 0)
	{
		#
		# Exit if rollerd isn't running.
		#
		if($ret == 255)
		{
			exit(1);
		}

		$rollcmd = "$rollctl -q -$args";
		system($rollcmd);

		#
		# If we were trying to roll a zone, we'll mark
		# it as having a problem and repaint its stripe.
		#
		if($rcmd eq "rollzsk")
		{
			$badzones{$zone} = 1;
			zonestripe($zone,'KSK',0);
		}
	}
	else
	{
		delete($badzones{$zone});
	}
}

#---------------------------------------------------------------------------
# Routine:	kskcmds()
#
# Purpose:	Handle the KSK Command menu commands.
#
sub kskcmds
{
	my $rcmd = shift;				# Command to run.
	my $ret;					# rollctl return code.

	#
	# Don't allow commands if -nomodify was given.
	#
	return if($nomodify);

	#
	# These commands need a zone.  We've got to query the user for
	# one, but until then they're unimplemented.
	#
	if($rcmd eq "dspub")
	{
		my $zone;				# Zone to handle.

		#
		# If a zone has been selected, we'll use it.  If not,
		# we'll prompt the user for the zone.
		#
		$zone = $selzone;
		if($selzone eq '')
		{
			$zone = getzone($rcmd);
			return if($zone eq "");
		}

		#
		# Move the selected zone into rollover phase 7.
		#
		$ret = system("$rollctl -q -dspub \"$zone\"");
	}
	elsif($rcmd eq "dspuball")
	{
		#
		# Move this zone into rollover phase 7.
		#
		$ret = system("$rollctl -q -dspuball");
	}

	#
	# Exit if rollerd isn't running.
	#
	if(($ret >> 8) == 255)
	{
		exit(1);
	}
}

#---------------------------------------------------------------------------
# Routine:	commander()
#
# Purpose:	Handle the general commands.
#
sub commander
{
	my $rcmd = shift;				# Command to execute.

	#
	# Build and execute the command string.
	#
	$rcmd = "$rollctl -q -$rcmd";
	system($rcmd);

	#
	# Wait a short bit and exit if rollerd hasn't killed us.
	#
	if($rcmd eq "halt")
	{
		sleep(5);
		exit(0);
	}
}

#---------------------------------------------------------------------------
# Routine:	zdisp_setter()
#
# Purpose:	Handle the Display All/None menu choices.
#
sub zdisp_setter
{
	my $dispstate = shift;				# Display state.

# print "zdisp_setter:  down in\n";

	#
	# Set the internal display state for all zones.
	#
	foreach my $zone (keys(%display))
	{
		$display{$zone} = $dispstate;
	}

	#
	# Lock and load the rollrec file.
	#
	rollrec_lock();
	rollrec_read($rrfile);

	#
	# Set the display state in the rollrec file.
	#
	foreach my $rname (rollrec_names())
	{
		rollrec_setval($rname,'display',$dispstate);
	}

	#
	# Let others use the rollrec file.
	#
	rollrec_close();
	rollrec_unlock();

	#
	# Update the screen.
	#
	calcrows();
	repaint();
}

#---------------------------------------------------------------------------
# Routine:	zdisp_select()
#
# Purpose:	Display the zone-display window and let the user select
#		which zones to display or hide.
#
sub zdisp_select
{
	my $subwin;					# Display dialog.
	my $znframe;					# Scrolled frame.
	my $wdgt;					# Widget for window.

	my %zonebox;					# Zone checkboxes.

	#
	# Create a new dialog box to hold our help info.
	#
	$subwin = $wm->Dialog(-title => "Zone Display Selection",
			      -default_button => "Okay",
			      -height => "10",
			      -buttons => ["Okay", "Cancel"]);

	#
	# Put a label in the label stripe.
	#
	$wdgt = $subwin->Label(-text => 'Checked zones will be displayed');
	$wdgt->pack();

	#
	# Now make the containers for the window.
	#
	$znframe = $subwin->Scrolled("Frame", -scrollbars => 'oe');
	$znframe->pack(-anchor	=> 'w',
		       -side	=> 'top',
		       -fill	=> 'both',
		       -expand	=> 1);

	#
	# Add the zone names to the scrolled box.
	#
	%zonebox = ();
	foreach my $zn (sort(keys(%display)))
	{
		$zonebox{$zn} = $znframe->Checkbutton(-text => $zn,
						    -variable => \$display{$zn},
						    -anchor => 'nw',
						);
		$zonebox{$zn}->pack(-side => 'top', -fill => 'x', -expand => 1);
	}

	$znframe->pack(-anchor => 'n', -side => 'top',
		       -fill => 'both', -expand => 1);

	#
	# Display our modal dialog.
	# 
	return if($subwin->Show() eq "Cancel");

	#
	# Lock and load the rollrec file.
	#
	rollrec_lock();
	rollrec_read($rrfile);

	#
	# Set the display state in the rollrec file.
	#
	foreach my $zn (rollrec_names())
	{
		rollrec_setval($zn,'display',$display{$zn});
	}

	#
	# Let others use the rollrec file.
	#
	rollrec_close();
	rollrec_unlock();

	#
	# Update the screen.
	#
	calcrows();
	repaint();
}

##############################################################################
#
# Screen-drawing routines
#
##############################################################################

#---------------------------------------------------------------------------
# Routine:	painter()
#
# Purpose:	Adjust the screen-painting count as needed.  If we've hit
#		a maximum value, destroy and rebuild the zone table.
#
sub painter
{
	#
	# Set the paint-count to a user-specified value, or increment
	# the current value if one wasn't given.
	#
	if(@_ > 0)
	{
		$paintcount = shift;
	}
	else
	{
		$paintcount++;
	}

	#
	# If we've exceeded our maximum, reset the paint-count and rebuild
	# the zone's table.
	#
	if($paintcount > $PAINTMAX)
	{
		$paintcount = 0;
		buildtable();
	}
}

#---------------------------------------------------------------------------
# Routine:	maketable()
#
# Purpose:	Create the zone status table.
#
sub maketable
{
	$zonetab = $body->Table(-rows		=> 12,
				-columns	=> $numcols,
				-scrollbars	=> 'e',
				-relief		=> 'raised',
				-borderwidth	=> 1,
				-fixedrows	=> 0,
				-takefocus	=> 1,
			       );
}

#---------------------------------------------------------------------------
# Routine:	buildtable()
#
# Purpose:	Rebuild the zone status table.  This also re-reads the
#		current rollrec file, so the zones listed may increase
#		or shrink depending on the state of that file..
#
sub buildtable
{
	#
	# Destroy the zone table's widgets.
	#
	if($zonetab)
	{
		$zonetab->clear;
		$zonetab->destroy;
	}

	#
	# Create a brand new table.
	#
	$zonetab = maketable();

	#
	# Re-populate and update the table.
	#
	readrrf($rrfile,1);
	$zonetab->update();

	#
	# Pack it all up.
	#
	$zonetab->pack(-fill => 'both', -expand => 1);
	$body->pack(-fill => 'both', -expand => 1);
}

#---------------------------------------------------------------------------
# Routine:	readrrf()
#
# Purpose:	Read a rollrec file and put the info on the screen.
#
sub readrrf
{
	my $rrf	     = shift;			# Rollrec to read.
	my $timeflag = shift;			# Flag for using current time.

	my $rr;					# Rollrec reference.

# print "readrrf:  down in\n";

	#
	# Initialize some data.
	#
	@zonenames = ();
	%zonenames = ();

	#
	# Pretty-up the window.
	#
	settitle($rrf);
	infostripe();
	headerstripe();

	#
	# Get the rollrec contents and the names of the zones it contains.
	#
	rollrec_read($rrf);
	@zonenames = rollrec_names();

	foreach my $zone (@zonenames)
	{
		my $krf;				# Keyrec file.
		my $kr;					# Keyrec reference.
		my $phase;				# Rollover phase.
		my $rolltype;				# Rollover type.

		#
		# Read the zone's rollrec record.
		#
		$rr = rollrec_fullrec($zone);

		#
		# Save the *actual* zonename.  $zone is the name of the
		# rollrec entry; $zonenames{$zone} is the 'zonename' field
		# in the rollrec.
		#
		$zonenames{$zone} = $zone;
		if(defined($rr->{'zonename'}))
		{
			$zonenames{$zone} = $rr->{'zonename'}
		}

		#
		# Save the current phase and rollover type.
		#
		$phase  = $rr->{'zskphase'};
		$rolltype = 'ZSK';
		if($phase == 0)
		{
			$phase  = $rr->{'kskphase'};
			$rolltype = 'KSK';
			$rolltype = '' if($phase == 0);
		}

		#
		# Save the zone's index and add it (or don't) to our list
		# of roll-enabled zones.
		#
		$zones{$zone} = $zonecnt++;
		if($rr->{'rollrec_type'} eq "roll")
		{
			$rollers{$zone} = 1;
		}
		else
		{
			$rollers{$zone} = 0;
		}

		#
		# Read the zone's keyrec and save the filename.
		#
		$krf = $rr->{'keyrec'};
		if(defined($rr->{'directory'}))
		{
			$krf = "$rr->{'directory'}/$krf";
		}
		$keyrecs{$zone} = $krf;

		#
		# Don't add this zone if it shouldn't be displayed.
		# If there isn't a display entry in the zone's rollrec,
		# we'll assume it should be displayed.
		#
		$display{$zone} = 1;
		if(defined($rr->{'display'}) && ($rr->{'display'} == 0))
		{
			$display{$zone} = 0;
		}
		next if($display{$zone} == 0);

		#
		# Figure out where each zone's stripe starts.
		#
		calcrows();

		#
		# Paint the window with this zone's info.
		#
		if($timeflag)
		{
			zonestripe($zone,$rolltype,$phase,$zonechron{$zone});
		}
		else
		{
			zonestripe($zone,'',$phase);
		}
	}

	#
	# Housekeeping before returning.
	#
	infostripe();
	rollrec_close();
	$zonetab->update();
}

#---------------------------------------------------------------------------
# Routine:	infostripe()
#
# Purpose:	Puts an information line in the top table row.
#
sub infostripe
{
	my $lab;
	my $rollmsg;				# Message about rolled zones.
	my $skipmsg;				# Message about skipped zones.
	my $timestr;				# rollerd's sleep time.

	setcounts();

	$lab = $zonetab->Label(-text => $title,
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$RRFCOL,$lab);

	$lab = $zonetab->Label(-text => ' ',
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$INFO1,$lab);

	$lab = $zonetab->Label(-text => $rollcntmsg,
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$ROLLCNTCOL,$lab);

	$lab = $zonetab->Label(-text => $skipcntmsg,
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$SKIPCNTCOL,$lab);

	open(RINFO,"$rollctl -status |");
	while(<RINFO>)
	{
		my $line;			# Input line from rollctl.
		my $key;			# Entry key from line.
		my $value;			# Entry value from line.

		$line = $_;
		chomp($line);

		$line =~ /^(.*?):\W*(.*)$/;
		$key = $1;
		$value = $2;

		if($key eq "sleeptime")
		{
			$timestr = fuzzytimetrans($value);
			$timestr =~ s/\.0//;
			$timestr = "sleep-time:  $timestr";
			last;
		}
	}
	close(RINFO);

	$lab = $zonetab->Label(-text => $timestr,
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$SLEEPCOL,$lab);

	$lab = $zonetab->Label(-text => ' ',
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$INFO5,$lab);

	$lab = $zonetab->Label(-text => ' ',
			       -font => $font, -background => $INFOBG);
	$zonetab->put($INFOROW,$INFO6,$lab);

	painter();
}

#---------------------------------------------------------------------------
# Routine:	headerstripe()
#
# Purpose:	Puts a column header line in the top table row.
#
sub headerstripe
{
	my $lab;

	$lab = $zonetab->Label(-text => 'Rollrec Name ',
			       -font => $font, -background => 'tan');
	$zonetab->put($TITLEROW,$NAMECOL,$lab);

	$lab = $zonetab->Label(-text => 'Zone Name    ',
			       -font => $font, -background => 'tan');
	$zonetab->put($TITLEROW,$ZONECOL,$lab);

	$lab = $zonetab->Label(-text => 'Status',
			       -font => $font, -background => 'tan');
	$zonetab->put($TITLEROW,$phasecol,$lab);

	if($showzsksets)
	{
		$lab = $zonetab->Label(-text => 'ZSK Type    ',
				       -font => $font, -background => 'tan');
		$zonetab->put($TITLEROW,$zsklblcol,$lab);

		$lab = $zonetab->Label(-text => 'ZSK Name    ',
				       -font => $font, -background => 'tan');
		$zonetab->put($TITLEROW,$zskkeycol,$lab);
	}

	if($showksksets)
	{
		$lab = $zonetab->Label(-text => 'KSK Type    ',
				       -font => $font, -background => 'tan');
		$zonetab->put($TITLEROW,$ksklblcol,$lab);

		$lab = $zonetab->Label(-text => 'KSK Name    ',
				       -font => $font, -background => 'tan');
		$zonetab->put($TITLEROW,$kskkeycol,$lab);
	}
}

#---------------------------------------------------------------------------
# Routine:	zonestripe()
#
# Purpose:	Puts a line for the specified zone in the table.
#
sub zonestripe
{
	my $zone     = shift;				# Zone to add.
	my $rolltype = shift;				# Zone's rollover type.
	my $phase    = shift;				# Zone's rollover phase.
	my $chronos  = shift || " ";			# Expiration-time data.

	my $krf;					# Zone's keyrec file.
	my $zonename;					# Actual zonename.
	my $fgcolor  = $NORMALFG;			# Foreground color.
	my $bgcolor;					# Background color.
	my $phasecolor;					# Color of phase column.
	my $phasestr;					# Phase description.

	my $row0;					# First row in group.
	my $row1;					# Second row in group.
	my $row2;					# Third row in group.

	my $zskcur;					# Current ZSK.
	my $zskpub;					# Published ZSK.
	my $zsknew;					# New ZSK.
	my $kskcur;					# Current KSK.
	my $kskpub;					# Published KSK.

	my $blank;					# Blank label.
	my $lab;					# Non-blank label.
	my $zonetxt;					# Zone name.
	my $phasedescr;					# Roll phase descrip.

	#
	# Don't account for this zone if it shouldn't be displayed.
	#
	return if($display{$zone} == 0);

	#
	# Get this zone's row group and row indices.
	#
	$row0 = $zonerows{$zone};
	return if($row0 == 0);
	$row1 = $row0 + 1;
	$row2 = $row0 + 2;

	#
	# Get this zone's background color.
	#
	$bgcolor    = getcolor($zone,$rolltype,$phase,1);
	$phasecolor = getcolor($zone,$rolltype,$phase,0);

	#
	# Get this zone's foreground color.
	#
	$fgcolor = $SELECTEDFG if($zone eq $selzone);

	#
	# Save this zone's data in case we need a repaint.
	#
	$zonephase{$zone} = $phase;
	$zonechron{$zone} = $chronos;

	#
	# Set the zone name's field.  (We're setting this here so it can
	# be modified in a single place.)
	#
	$zonetxt  = "$zone    ";

	#
	# Set the rollover description field.
	#
	$phasedescr = $zphasedescr[$phase];
	if($rolltype eq 'KSK')
	{
		$phasedescr = $kphasedescr[$phase];
	}

	#
	# Get the keyrec file and actual zone name.
	#
	$krf = $keyrecs{$zone};
	keyrec_read($krf);
	$zonename = $zonenames{$zone};

	#
	# Handle skipped zones here.  We won't do anything if skipped zones
	# shouldn't be displayed.  Otherwise, we'll just put up a single line.
	#
	#  col 0    col 1    col 2     col 3      col 4      col 5      col 6
	#  rollrec  zone    "skipped"  <blank>    <blank>    <blank>    <blank>
	#  name     name
	#
	if(($rollers{$zone} == 0) || ($phase == -42))
	{
		my $zlabel = 'skipping';			# Zone's label.

		return if($noskip);

		#
		# Set up some data for zones that have problems.
		#
		if(defined($badzones{$zone}))
		{
			$zlabel = 'ERROR -- check rollerd log';
			$bgcolor = $ERRORBG;
		}

		$lab = $zonetab->Label(-text => $zonetxt, -font => $font,
				       -foreground => $fgcolor,
				       -background => $bgcolor);
		$zonetab->put($row0,$NAMECOL,$lab);

		$lab = $zonetab->Label(-text => $zonename, -font => $font,
				       -foreground => $fgcolor,
				       -background => $bgcolor);
		$zonetab->put($row0,$ZONECOL,$lab);

		$lab = $zonetab->Label(-text => $zlabel,
				       -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor);
		$zonetab->put($row0,$phasecol,$lab);

		if($showzsksets)
		{
			$blank = $zonetab->Label(-text => ' ',
						 -background => $bgcolor);
			$zonetab->put($row0,$zsklblcol,$blank);

			$blank = $zonetab->Label(-text => ' ',
						 -background => $bgcolor);
			$zonetab->put($row0,$zskkeycol,$blank);
		}

		if($showksksets)
		{
			$blank = $zonetab->Label(-text => ' ',
						 -background => $bgcolor);
			$zonetab->put($row0,$ksklblcol,$blank);

			$blank = $zonetab->Label(-text => ' ',
						 -background => $bgcolor);
			$zonetab->put($row0,$kskkeycol,$blank);
		}

		#
		# Update the display.
		#
		$zonetab->update();
		return;
	}

	############################################
	#
	# First column:  rollrec name
	#
	$blank = $zonetab->Label(-text => ' ', -background => $bgcolor);
	$zonetab->put($row0,0,$blank);
	$lab = $zonetab->Label(-text => $zonetxt, -font => $font,
			       -foreground => $fgcolor,
			       -background => $bgcolor);

	$zonetab->put($row1,$NAMECOL,$lab);
	$blank = $zonetab->Label(-text => ' ', -font => $font,
				 -background => $bgcolor);
	$zonetab->put($row2,$NAMECOL,$blank);

	############################################
	#
	# Second column:  zone name
	#
	$blank = $zonetab->Label(-text => ' ', -background => $bgcolor);
	$zonetab->put($row0,$ZONECOL,$blank);
	$lab = $zonetab->Label(-text => $zonename, -font => $font,
			       -foreground => $fgcolor,
			       -background => $bgcolor);

	$zonetab->put($row1,$ZONECOL,$lab);
	$blank = $zonetab->Label(-text => ' ', -font => $font,
				 -background => $bgcolor);
	$zonetab->put($row2,$ZONECOL,$blank);

	###########################################
	#
	# Third column:  zone rollover phase
	#
	$phasestr = "$rolltype phase $phase";
	$phasestr = " " if($phase == 0);
	$lab = $zonetab->Label(-text => $phasestr, -font => $font,
			       -foreground => $NORMALFG,
			       -background => $phasecolor);
	$zonetab->put($row0,$phasecol,$lab);
	$lab = $zonetab->Label(-text => $phasedescr,
			       -font => $font,
			       -foreground => $NORMALFG,
			       -background => $phasecolor);
	$zonetab->put($row1,$phasecol,$lab);
	$lab = $zonetab->Label(-text => $chronos, -font => $font,
			       -foreground => $NORMALFG,
			       -background => $phasecolor);
	$zonetab->put($row2,$phasecol,$lab);

	###########################################
	#
	# Fourth and fifth columns:  ZSKs
	#
	if($showzsksets)
	{
		#
		# Get the zone's key names.
		#
		$zskcur = keyrec_recval($zonename,'zskcur');
		$zskpub = keyrec_recval($zonename,'zskpub');
		$zsknew = keyrec_recval($zonename,'zsknew');

		#
		# Add some spacing to the fields.
		#
		$zskcur	 .= "    ";
		$zskpub	 .= "    ";
		$zsknew	 .= "    ";

		#
		# Third column:  key label
		#
		$lab = $zonetab->Label(-text => "Current   ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row0,$zsklblcol,$lab);
		$lab = $zonetab->Label(-text => "Published   ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row1,$zsklblcol,$lab);
		$lab = $zonetab->Label(-text => "New   ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row2,$zsklblcol,$lab);

		#
		# Fourth columns:  key name
		#
		$lab = $zonetab->Label(-text => $zskcur, -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row0,$zskkeycol,$lab);

		$lab = $zonetab->Label(-text => $zskpub, -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row1,$zskkeycol,$lab);
		$lab = $zonetab->Label(-text => $zsknew, -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row2,$zskkeycol,$lab);
	}

	###########################################
	#
	# Sixth and seventh columns:  KSKs
	#
	if($showksksets)
	{
		#
		# Get the zone's key names.
		#
		$kskcur = keyrec_recval($zonename,'kskcur');
		$kskpub = keyrec_recval($zonename,'kskpub');

		#
		# Add some spacing to the fields.
		#
		$kskcur	 .= "    ";
		$kskpub	 .= "    ";

		#
		# Fifth column:  KSK keyset label
		#
		$lab = $zonetab->Label(-text => "Current   ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row0,$ksklblcol,$lab);
		$lab = $zonetab->Label(-text => "Published   ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row1,$ksklblcol,$lab);
		$lab = $zonetab->Label(-text => " ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row2,$ksklblcol,$lab);

		#
		# Sixth columns:  KSK keyset name
		#
		$lab = $zonetab->Label(-text => $kskcur, -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row0,$kskkeycol,$lab);
		$lab = $zonetab->Label(-text => $kskpub, -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row1,$kskkeycol,$lab);
		$lab = $zonetab->Label(-text => " ", -font => $font,
				       -foreground => $NORMALFG,
				       -background => $bgcolor, -anchor => 'w');
		$zonetab->put($row2,$kskkeycol,$lab);
	}

	###########################################
	#
	# Update the display.
	#
	keyrec_close();
	$zonetab->update();
}

#----------------------------------------------------------------------
# Routine:      repaint()
#
# Purpose:      Redisplay the current zone data.
#
sub repaint
{
	foreach my $zn (sort(@zonenames))
	{
		#
		# Don't account for this zone if it shouldn't be displayed.
		#
		next if($display{$zn} == 0);

		#
		# Add in a stripe for this zone.
		#
		zonestripe($zn,$zoneroll{$zn},$zonephase{$zn},$zonechron{$zn});
	}
}

##############################################################################
#
# Utility routines
#
##############################################################################

#---------------------------------------------------------------------------
# Routine:	rollerdcmd()
#
# Purpose:	Handle commands from rollerd.
#
sub rollerdcmd
{
	my $line;					# Input from rollerd.
	my $cmd;					# Command.
	my $zone;					# Command's zone.
	my $phase;					# Command's phase.

	#
	# Get the data from rollerd.
	#
	$line = <STDIN>;
	chomp($line);

	#
	# Break the line into its pieces.
	#
	$line =~ /^([a-z]+)\W+([a-zA-Z0-9\.\+\-_,:\/ 	]+)\W+([0-9]+)/;
	$cmd	= $1;
	$zone	= $2;
	$phase	= $3;

	#
	# Handle the commands from rollerd:
	#
	#	expiration	zone will soon expire
	#	halt		exit
	#	nop		do nothing
	#	kskphase	change the specified zone's KSK phase
	#	zskphase	change the specified zone's ZSK phase
	#	startroll	start a zone in rollover
	#	stoproll	stop a zone's rollover
	#	badzone		mark zone as having problems
	#
	if($cmd eq "zskphase")
	{
		$zoneroll{$zone} = 'ZSK';
		goodzone($zone);

		zonestripe($zone,'ZSK',$phase);
	}
	if($cmd eq "kskphase")
	{
		$zoneroll{$zone} = 'KSK';
		goodzone($zone);
		zonestripe($zone,'KSK',$phase);
	}
	elsif($cmd eq "halt")
	{
		exit(0);
	}
	elsif($cmd eq "startroll")
	{
		rollrec_read($rrfile);
		$phase = rollrec_recval($zone,'zskphase');
		rollrec_close();

		$rollers{$zone} = 1;
		calcrows();

		infostripe();

		$zonephase{$zone} = $phase;
		goodzone($zone);
		repaint();
	}
	elsif($cmd eq "stoproll")
	{
		goodzone($zone);
		$rollers{$zone} = 0;
		calcrows();
		$zonephase{$zone} = -42;

		infostripe();

		repaint();
	}
	elsif($cmd eq "expiration")
	{
		my $rolltype;				# Type of rollover.
		my $secs;				# Additional data.
		my $timestr;				# Translated time.

		$line =~ /^([a-z]+)\W+([a-zA-Z0-9\.\+\-]+)\W+([0-9]+)\W+([a-zA-Z]+)\W+(.*)$/;

		$rolltype = $4;
		$secs	  = $5;
		$timestr  = fuzzytimetrans($secs);
		$timestr  =~ s/\.0//;

		goodzone($zone);
		$timestr = "starting roll in $timestr" if($phase == 0);
		zonestripe($zone,$rolltype,$phase,$timestr);
		$zoneroll{$zone} = '';
	}
	elsif($cmd eq "rollrec")
	{
		$rrfile = $zone;
		buildtable();
		return;
	}
	elsif($cmd eq "sleeptime")
	{
		infostripe();
		return;
	}
	elsif($cmd eq "badzone")
	{
		$badzones{$zone} = 1;
		zonestripe($zone,'KSK',0);
		return;
	}
	elsif($cmd eq "nop")
	{
		return;
	}

	#
	# Increment the screen-paint count and update the zone table.
	#
	painter();
	$zonetab->update();
}

#----------------------------------------------------------------------
# Routine:      selector()
#
# Purpose:      Register a mouse-selected zone name.
#
sub selector
{
	my $argv = shift;				# Argument reference.
	my %argv = %$argv;				# Argument hash.

	my $wijname;					# Event's widget.
	my @pieces;					# Pieces of array.

	my $found = 0;					# Found-row flag.
	my $selx;					# Selection x-coord.
	my $wij;					# Selection's widget.

	my @zonearr = ();				# Temp. zone/row array.

	#
	# Make sure we're clicking in our table and not in a menu
	# or frame.
	#
	#	(This ugly bit of coding depends on our only
	#	ever making a single table.)
	#
	$wijname = $argv{'_TkValue_'};
	@pieces = split /\./, $wijname;

	#
	# If the selected widget isn't a table, we'll unselect the
	# currently selected widget.
	#
	if($pieces[2] ne "table")
	{
		if($selwidget && ($pieces[2] !~ /menubutton/))
		{
			$selwidget->configure(-foreground => $NORMALFG);
			$selzone = '';
			$selwidget = undef;

		}
		return;
	}

	#
	# Find the widget that has the same name as the event window.
	#
	for(my $xind=0; $xind < $lastrow; $xind++)
	{
		for(my $yind=0; $yind < $MAXCOLS; $yind++)
		{
			my $pn;				# Widget's pathname.

			#
			# Get this x,y widget and its name.
			#
			$wij = $zonetab->get($xind,$yind);
			$pn = $wij->PathName;

			#
			# Go to the next widget if this isn't it.
			#
			next if($pn ne $wijname);

			#
			# Save the index and drop out.
			#
			$selx = $xind;
			$found = 1;
			last;
		}

		last if($found);
	}

	#
	# Nothing gets selected if the user didn't select anything.
	# Obviously.
	#
	return if(!$found);

	#
	# Clear the selection and return if the click came in the
	# information row or the title row.
	#
	if(($selx == $INFOROW) || ($selx == $TITLEROW))
	{
		$selwidget->configure(-foreground => $NORMALFG) if($selwidget);
		$selwidget = undef;

		$selzone = '';
		return;
	}

	#
	# Build an array of zone-names, indexed by the zone's first row.
	#
	foreach my $zone (keys(%zonerows))
	{
		$zonearr[$zonerows{$zone}] = $zone;
	}

	#
	# Mark the proper zone name (row N, column 0) with a highlighted
	# marking.  All others will be unhighlighted.  We'll work backwards
	# in our newly constructed zone-name array and find the zone's name
	# in the array by working backwards from the index.
	#
	for(my $i=$selx; $i > 0; $i--)
	{
		#
		# Skip any blank widgets.
		#
		next if($zonearr[$i] eq '');

		#
		# Get the zone string's widget and set the selection
		# color for each zone in the zone column.
		#
		for(my $rind=0; $rind < $lastrow; $rind++)
		{
			my $wij;			# Zone's widget.
			my $zstr;			# Widget's zone.

			#
			# Get this row's column-zero widget.  Go to the
			# next if there isn't a zone in it.
			#
			$wij = $zonetab->get($rind,0);
			$zstr = $wij->cget('-text');
			$zstr =~ s/[ ]*$//;
			next if($zstr eq '');

			#
			# If this is our selected zone, mark the zone
			# string as highlighted.  All others go to the
			# normal unhighlighted look.
			#
			if($zstr eq $zonearr[$i])
			{
				$wij->configure(-foreground => $SELECTEDFG);
				$selwidget = $wij;
			}
			else
			{
				$wij->configure(-foreground => $NORMALFG);
			}
		}

		#
		# Select this zone.
		#
		$selzone = $zonearr[$i];
		last;
	}

}

#----------------------------------------------------------------------
# Routine:      calcrows()
#
# Purpose:      Calculate the starting row for each displayed zone.
#
sub calcrows
{
	my $row = $STARTROW;				# Starting-row index.

	#
	# Zap the zone row index table and zone-colors table.
	#
	%zonerows = ();
	%zonecolors = ();

	#
	# Reset the color index and initialize the last-row index.
	#
	$colorind = 0;
	$lastrow = $STARTROW;			# Final row index.

	#
	# Sort the zone-names list and set the zone row index table
	# according to whether or not each zone is displayed.
	#
	foreach my $zn (sort(@zonenames))
	{
		#
		# Save the row index for the zone name.
		#
		$zonerows{$zn} = $row;

		#
		# Don't account for this zone if it shouldn't be displayed
		# or is a skipped zone.
		#
		if(($noskip && ($rollers{$zn} == 0)) ||
		   ($display{$zn} == 0))
		{
			$zonerows{$zn} = 0;
			next;
		}

		#
		# Bump the row count by the appropriate amount, depending
		# on if it's a rolled or skipped zone.  Rolled zones also
		# get color information.
		#
		if($rollers{$zn} == 0)
		{
			$row += 1;
		}
		else
		{
			$row += $ROWINCR;

			$zonecolors{$zn} = $colorind;
			$colorind++;
			$colorind = 0 if($colorind == $NUMCOLORS);
		}

		#
		# Save the row index in case this is the last row.
		#
		$lastrow = $row;
	}

	#
	# Set the table to the new row size.
	#
	$zonetab->configure(-rows => $lastrow);
}

#---------------------------------------------------------------------------
# Routine:	getcolor()
#
# Purpose:	Figure out what color this zone's stripe needs.
#
sub getcolor
{
	my $zone = shift;		# Zone whose color we're getting.
	my $rolltype = shift;		# Zone's rollover type.
	my $phase = shift;		# Zone's phase.
	my $base = shift;		# Base-color flag.

	my $band;			# Zone's output band.
	my $color;			# Color array we'll use.
	my $colorname;			# Color to return.

	#
	# Return this skipped-zone color if this zone isn't rolling.
	#
	return($skipcolor) if(($rollers{$zone} != 1) || $nocolors);

	#
	# Get the background color for this zone in this phase.
	#
	$band = $zonecolors{$zone};

	#
	# Select the color group to use for the background, depending
	# on if this is a KSK or ZSK rollover.
	#
	if($rolltype eq 'KSK')
	{
		$color = $kphasecolors[$band];
	}
	else
	{
		$color = $zphasecolors[$band];
	}

	#
	# Get the background color for this zone in this phase and
	# return the color.
	#
	if($base || $noshading)
	{
		$colorname = $color->[0]
	}
	else
	{
		$colorname = $color->[$phase];
	}

	#
	# Get the background color for this zone in this phase and
	# return the color.
	#
	$colorname = $skipcolor if(!defined($colorname));

	#
	# Return the appropriate color name.
	#
	return($colorname);
}

#---------------------------------------------------------------------------
# Routine:	getnode()
#
# Purpose:	Return the last element in a path.
#
sub getnode
{
	my @pathelts;					# Path elements.
	my $pathnode;					# Last path elements.

	@pathelts = split /\//, $rrfile;
	$pathnode = pop @pathelts;

	return($pathnode);
}

#---------------------------------------------------------------------------
# Routine:	getzone()
#
# Purpose:	This routine creates a modal dialog box to allow the user
#		to select a zone to be rolled/skipped.  For to-be-rolled
#		zones, only those zones are listed which are currently
#		skipped.  Similarly, for to-be-skipped zones, only those
#		zones are listed which are currently being rolled.
#
sub getzone
{
	my $op = shift;					# Operation for zone.
	my $opflag = 0;					# Operation flag.

	my $dlg;					# Dialog widget.
	my $zlist;					# Zone listbox.

	my $listcnt = 0;				# Count of listed zones.

	my $ret;					# Dialog's return value.
	my @indarr;					# Listbox' select array.
	my $selzone;					# Selected zone.
	my @selzones;					# Selected zones.

	my $height = $DLGHEIGHT;			# Height of dialog box.
	my $zonecount = @zonenames;			# Number of zones.

	#
	# Set the zone operation flag.
	#
	$opflag = 1 if($op eq "rollzsk");

	#
	# Massage the zone operation for the dialog box's title.
	#
	$op =~ s/zsk//;
	$op = ucfirst($op);

	#
	# Set the dialog box height.
	#
	$height = $zonecount if($zonecount < $height);

	#
	# Build and configure the dialog box and the widgets it contains.
	#
	$dlg = $wm->Dialog(-title => "Select Zone to $op", -text  => '',
			   -default_button => "Okay",
			   -buttons => ["Okay", "Cancel"]);
	$zlist = $dlg->Listbox(-relief	    => 'raised',
			       -borderwidth => 1,
			       -selectmode  => 'single');
	$dlg->AddScrollbars($zlist);
	$dlg->configure(-scrollbars => 'e');

	#
	# Add the zone names to the dialog's listbox.  However, we'll
	# only add rolling zones to a skip-zone command and we'll
	# only add skipped zones to a roll-zone command.
	#
	foreach my $zone (sort(@zonenames))
	{
		if((!$opflag && ($rollers{$zone} == 1))		||
		   ( $opflag && ($rollers{$zone} == 0)))
		{
			$zlist->insert("end",$zone);
			$listcnt++;
		}
	}

	#
	# If there weren't any zones added, we'll return 'cause there's
	# nothing for us to do.  The pack() is to keep Tk from whining.
	#
	if($listcnt == 0)
	{
		$zlist->pack(-fill => 'x', -expand => 1);
		return;
	}

	#
	# Pack 'er up and display the zone list.
	#
	$zlist->pack(-fill => 'x', -expand => 1);
	$ret = $dlg->Show();

	#
	# Go no further if no zone was selected.
	#
	return("") if($ret eq "Cancel");

	#
	# Dig out the selected zone and return it.
	#
	@indarr = $zlist->curselection;
	return("") if(@indarr == 0);
	@selzones = $zlist->get($indarr[0]);
	$selzone = $selzones[0];
	return($selzone);
}

#----------------------------------------------------------------------
# Routine:      goodzone()
#
# Purpose:      Ensure that this zone is considered good.
#
sub goodzone
{
	my $zone = shift;				# Zone to mark as good.

	return if(!defined($badzones{$zone}));

	$rollers{$zone} = 1;
	calcrows();
	delete($badzones{$zone});
}

#---------------------------------------------------------------------------
# Routine:	readconfig()
#
# Purpose:      Read our configuration file.  Config entries are "field value"
#		pairs, with the following fields recognized:
#
#			fontsize	size of demo output font
#			skipcolor	color to use for skip records
#			modify		allow modification commands
#			shading		shade the status columns
#			showskip	show skipped zones
#
sub readconfig
{
	my $line;					# Configuration line.
	my $field;					# Field name from line.
	my $value;					# Field value from line.

	#
	# Return if we don't have a configuration file.
	#
	return if(!-e $BLCONFIG);

	#
	# Complain and return if we can't read our configuration file.
	#
	if(!-r $BLCONFIG)
	{
		print STDERR "unable to read $BLCONFIG; continuing...\n";
		return;
	}

	#
	# Read the config file, ignoring comment lines.  Each non-comment
	# line is expected to be in a "field value" pair.  As we read each
	# line, we'll take the appropriate action.
	#
	open(CONF,"<$BLCONFIG");
	while(<CONF>)
	{
		$line = $_;
		chomp($line);

		#
		# Skip empty and comment lines.
		#
		$line =~ s/^[ \t]+//;
		next if(($line =~ /^\#/) || ($line =~ /^$/));

		#
		# Pull out the field and value from the line.
		#
		$line =~ /([a-zA-Z_\-]+)\W+([a-zA-Z0-9_\-]+)/;
		$field = lc($1);
		$value = $2;

		#
		# Set the config value.
		#
		setopt($field,$value);
	}

	close(CONF);
}

#----------------------------------------------------------------------
# Routine:      setopt()
#
# Purpose:      Set the appropriate option from the specified field.
#		This is done here in order to centralize things for both
#		handling the DNSSEC-Tools configuration file and users'
#		configuration files.
#
#		The following fields are recognized:
#			colors		use different colors for stripes
#			fontsize	size of demo output font
#			modify		allow modification commands
#			shading		shade the status columns
#			showskip	show skipped zones
#			skipcolor	color to use for skip records
#
sub setopt
{
	my $field = shift;				
	my $value = shift;				

	#
	# Handle the field values appropriately.
	#
	if($field eq "fontsize")
	{
		if($value =~ /[a-zA-Z\_\-]/)
		{
			print STDERR "invalid fontsize \"$value\" in configuration file\n";
			next;
		}
		$fontsize = $value;
		setfont();
	}
	elsif($field eq "modify")
	{
		my $ret = flagval($value);

		if($ret) { $nomodify = 0; }
		else	 { $nomodify = 1; }
	}
	elsif($field eq "colors")
	{
		my $ret = flagval($value);

		if($ret) { $nocolors = 0; }
		else	 { $nocolors = 1; }
	}
	elsif($field eq "shading")
	{
		my $ret = flagval($value);

		if($ret) { $noshading = 0; }
		else	 { $noshading = 1; }
	}
	elsif($field eq "showskip")
	{
		my $ret = flagval($value);

		if($ret) { $noskip = 0; }
		else	 { $noskip = 1; }
	}
	elsif($field eq "skipcolor")
	{
		$skipcolor = $value;
	}
}

#----------------------------------------------------------------------
# Routine:      flagval()
#
# Purpose:      Translate a flag value into a numeric.
#
sub flagval
{
	my $val = shift;			# Flag value to translate.
	my $ret = 0;				# Return value.

	$val = lc($val);

	if(($val != 0)		||
	   ($val eq "on")	||
	   ($val eq "y")	||
	   ($val eq "ye")	||
	   ($val eq "yes"))
	{
		$ret = 1;
	}

	return($ret);
}

#----------------------------------------------------------------------
# Routine:      setfont()
#
# Purpose:      Set the $font global variable.
#
sub setfont
{
	$font = "*-*-bold-r-*-*-$fontsize-*-*-*-*-*-*-*";
}


#----------------------------------------------------------------------
# Routine:      setcounts()
#
# Purpose:      Set the rolled zone and skipped zone messages with the
#		current counts.
#
sub setcounts
{
	my $total = 0;					# Total zone count.
	my $rollcnt = 0;				# Rolled zone count.
	my $skipcnt = 0;				# Skipped zone count.

	#
	# Calculate the number of rolled and skipped zones.
	#
	$total = keys(%rollers);
	foreach my $zn (keys(%rollers))
	{
		$rollcnt++ if($rollers{$zn});
	}

	$skipcnt = $total - $rollcnt;

	#
	# Set the messages, in a grammatically anal-retentive manner.
	#
	if($rollcnt != 0)
	{
		if($rollcnt == 1)
		{
			$rollcntmsg = "$rollcnt Rolling Zone";
		}
		else
		{
			$rollcntmsg = "$rollcnt Rolling Zones";
		}
	}
	else
	{
		$rollcntmsg = "0 Rolling Zones";
	}

	if($skipcnt != 0)
	{
		if($skipcnt == 1)
		{
			$skipcntmsg = "$skipcnt Skipped Zone";
		}
		else
		{
			$skipcntmsg = "$skipcnt Skipped Zones";
		}
	}
	else
	{
		$skipcntmsg = "0 Skipped Zones";
	}
}

#----------------------------------------------------------------------
# Routine:      settitle()
#
# Purpose:      Set the title for use in the "Monitoring File" line.
#
sub settitle
{
	my $name = shift;				# Name to use.

	$title = getnode($name);
}

#----------------------------------------------------------------------
# Routine:      rollerdup()
#
# Purpose:      Ensures that rollerd is running.
#
sub rollerdup
{
	my $rollcmd;					# rollctl command.
	my $ret;					# rollctl return code.

	#
	# Build and execute the command string.
	#
	$rollcmd = "$rollctl -q -status";
	$ret = system($rollcmd);

	#
	# If the command failed, we'll give an error and exit.
	#
	$ret = $ret >> 8;
	if(($ret == 254) || ($ret == 255))
	{
		print STDERR "blinkenlights:  unable to contact rollerd\n";
		exit(1);
	}
}

##############################################################################
#
# Utility-window routines.
#
##############################################################################


#---------------------------------------------------------------------------
# Routine:	help_help()
#
# Purpose:	Display the help window.
#
#		This is a text-only version of the pod.  The Perl/Tk
#		requirement section was removed because if the user is
#		seeing this, then they've already got Perl/Tk.
#
#		Changes to this text should be reflected in the pod.
#
sub help_help
{
	my $hframe;				# Help frame.
	my $wdgt;				# General widget.

	my $helpstr;				# The help message to display.
	my $helpwij;				# Text widget for help message.

    ####################    beginning of help text    ####################

	$helpstr = "

blinkenlights - DNSSEC-Tools rollerd GUI

SYNOPSIS
         
    blinkenlights <rollrec-file>

DESCRIPTION

blinkenlights is a GUI tool for use with monitoring and controlling the
DNSSEC-Tools rollerd program.  It displays information on the current state of
the zones rollerd is managing.  The user may control some aspects of rollerd's
execution using blinkenlights menu commands.

blinkenlights creates a window in which to display information about each zone
rollerd is managing.  (These zones are those in rollerd's current rollrec
file.) As a zone's rollover status changes, blinkenlights will update its
display for that zone.  Skipped zones, zones listed in the rollrec file but
which are not in rollover or normal operation, are displayed but have very
little useful information to display.

The user may also select a set of zones to hide from the display.  These
zones, if in the rolling state, will continue to roll; however, their zone
information will not be displayed.  Display state for each zone will persist
across blinkenlights executions.

Menu commands are available for controlling rollerd.  The commands which
operate on a single zone may be executed by keyboard shortcuts.  The zone
may be selected either by clicking in its \"zone stripe\" or by choosing from
a dialog box.  Display and execution options for blinkenlights are also
available through menu commands.  More information about the menu commands
is available in the MENU COMMANDS section.

blinkenlights is only intended to be started by rollerd, not directly by a
user.  There are two ways to have rollerd start blinkenlights.  First, rollctl
may be given the -display option.  Second, the -display option may be given on
rollerd's command line.

SCREEN LAYOUT

The blinkenlights window is laid out as a series of \"stripes\".  The top
stripe contains status information about rollerd, the second stripe
contains column headers, and the bulk of the window consists of zone stripes.
The list below provides more detail on the contents of each stripe.

See the WINDOW COLORS section for a discussion of the colors used for the
zone stripes.

	* rollerd information stripe

	  The information stripe contains four pieces of information:
	  rollerd's current rollrec file, the count of rolling zones, the
	  count of skipped zones, and the amount of time rollerd waits between
	  processing its queue.  Coincidentally, that last datum is also the
	  amount of time between blinkenlights screen updates.

	* column headers stripe

	  This stripe contains the column headers for the columns of
	  each zone stripe.

	* zone stripes

	  Each zone managed by rollerd (i.e., every zone in the current
	  rollrec file) will have a zone stripe which describes that
	  zone's current state.  The stripe is divided into three
	  sections:  the zone name, the current rollover state, and the
	  zone's DNSSEC keys.

	  The zone name section just contains the name of the zone.

	  The rollover state section contains the rollover phase number,
	  a text explanation of the phase, and the amount of time
	  remaining in that rollover phase.  The phase explanation is
	  \"normal operation\" when the zone isn't currently in rollover.

	  The DNSSEC key section contains the names of the Current,
	  Published, and New ZSK keys for that stripe's zone.

	  See the WINDOW COLORS section for a discussion of the colors
	  used for the zone stripes.

WINDOW COLORS

The default blinkenlights configuration uses window coloring to provide visual
cues and to aid in easily distinguishing zone information.  The default window
coloring behavior gives each zone stripe has its own color and the rollover
state section of each zone stripe is shaded to show the zone's phase.  Window
coloring can be turned off (and on) with configuration options and menu
commands.

The two window coloring behaviors are discussed more fully below:

	* zone stripe colors

	  Each rolling zone's stripe is given one of three colors:  blue, red,
	  or green.  The color is assigned on a top-down basis and the colors
	  wrap if there are more than three zones.  So, the first zone is
	  always blue, the second zone red, the third zone green, the fourth
	  zone blue, etc.

	  The colors do not stay with a particular zone.  If a rolling zone
	  becomes a skipped zone, the zone stripes will be reassigned new
	  colors to account for that skipped zone.

	  Skipped zones are not colored with these three colors.  Stripes for
	  skipped zones are colored either grey or a color set in the
	  configuration file.  If you choose to use a non-standard color for
	  skipped zones your should ensure that it is not one of the colors
	  used for rolling zones' stripes.  Modifying the skipcolor
	  configuration field allows the skipped-zone color to be changed.

	  The colors configuration field can be used to turn on or off the use
	  of colors for zone stripes.  If stripe coloring is turned off, then
	  every stripe will be displayed using the skipcolor color.

	* rollover-state shading

	  The only portion of a zone stripe that changes color is the status
	  column; the color of the rest of the zone stripe stays constant.
	  Before a zone enters rollover, the status column is the same color
	  as the rest of the stripe.  When the zone enters rollover, the
	  status column's color is changed to a very light shade of the
	  stripe's normal color.  As the rollover phases progress towards
	  rollover completion, the status column's shade darkens.  Once
	  rollover completes, the status column returns again to the same
	  shade as the rest of that stripe.

	  The shading configuration field can be used to turn on or off the
	  use of shading in the rollover-state column.  If shading is turned
	  off, then the zone stripe will be a solid color.

	  See the CONFIGURATION FILE section for information on setting the
	  configuration fields.


MENU COMMANDS

A number of menu commands are available to control the behavior of blinkenlights
and to send commands to rollerd.  These commands are discusses in this section.

	File Menu

		The commands in this menu are basic GUI commands.

		* Quit

		  blinkenlights will stop execution.

	Options Menu

		The commands in this menu control the appearance and behavior of
		blinkenlights.

		* Row Colors (toggle)

		  This menu item is a toggle to turn on or off the coloring of
		  zone stripes.  If row coloring is turned off, zone stripes
		  will all be the same color.  If row coloring is turned on,
		  zone stripes will be displayed in varying colors.  See the
		  WINDOW COLORS section for a discussion of row coloring.

		* Status Column Shading (toggle)

		  This menu item is a toggle to turn on or off the shading of
		  the zone status column.  If shading is turned off, the zone
		  stripes will present a solid, unchanging band of color for
		  each zone.  If shading is turned on, the color of the zone
		  status column will change according to the zone's rollover
		  state.

		* Skipped Zones Display (toggle)

		  This menu item is a toggle to turn on or off the display of
		  skipped zones.  If display is turned off, zone stripes for
		  skipped zones will not be displayed.  If display is turned
		  on, zone stripes for all zones will be displayed.

		* Modification Commands (toggle)

		  In some situations, it may be desirable to turn off
		  blinkenlights' ability to send commands to rollerd.  This
		  menu item is a toggle to turn on or off this ability.  If
		  the commands are turned off, then the \"Zone Control\" menu
		  and keyboard shortcuts are disabled.  If the commands are
		  turned on, then the \"Zone Control\" menu and keyboard
		  shortcuts are enabled.

		* Font Size

		  This menu item allows selection of font size of text
		  displayed in the main window.

		  Normally, changing the font size causes the window to grow
		  and shrink as required.  However, on Mac OS X there seems to
		  be a problem when the size selected increases the window
		  size to be greater than will fit on the screen.  If the font
		  size is subsequently reduced, the window size does not
		  shrink in response.

	Zone Control Menu

		The commands in this menu are GUI interfaces for the rollctl
		command.  Not all of the rollctl commands have interfaces --
		only those which directly affect zone management.

		* Roll Selected Zone

		  The selected zone will be moved to the rollover state.  This
		  only has an effect on skipped zones.  A zone may be selected
		  by clicking on its zone stripe.  If this command is selected
		  without a zone having been selected, a dialog box is displayed
		  from which a currently skipped zone may be chosen.

		* Roll All Zones

		  All zones will be moved to the rollover state.  This has no
		  effect on currently rolling zones.

		* Run the Queue

		  rollerd is awoken and runs through its queue of zones.  The
		  operation required for each zone is then performed.

		* Skip Selected Zone

		  The selected zone will be moved to the skipped state.  This
		  only has an effect on rolling zones.  A zone may be selected
		  by clicking on its zone stripe.  If this command is selected
		  without a zone having been selected, a dialog box is
		  displayed from which a currently rolling zone may be chosen.

		* Skip All Zones

		  All zones will be moved to the skipped state.  This has no
		  effect on currently skipped zones.

		* Halt Rollerd

		  rollerd's execution is halted.  As a result, blinkenlights'
		  execution will also be halted.

	Zone Display Menu

		The commands in this menu are GUI interfaces for displaying or
		hiding zone stripes.  The commands allow all, some, or none of
		the zone stripes to be displayed.  Undisplayed rolling zones
		will continue to roll, but they will do so without the
		blinkenlights window indicating this.

		* Zone Selection

	          A dialog box is created that holds a list of the zones
	          currently managed by rollerd.  The user may select which
	          zones should be displayed by clicking on the zone's checkbox.
	          Zones with a selected checkbox will be displayed; zones
	          without a selected checkbox will not be displayed.

		* Display All Zones

		  All zones will be displayed in the blinkenlights window.

		* Hide All Zones

		  No zones will be displayed in the blinkenlights window.

	Help Menu

		The commands in this menu provide assistance to the user.

		* Help

		  Display a window containing help information.

CONFIGURATION FILE

Several aspects of blinkenlights' behavior may be controlled from
configuration files.  Configuration value may be specified in the DNSSEC Tools
configuration file or in a more specific rc.blinkenlights.  The system-wide
blinkenlights configuration file is in the DNSSEC-Tools configuration
directory and is named blinkenlights.conf.  Multiple rc.blinkenlights
files may exist on a system, but only one in the directory in which
blinkenlights is executed is used.

The following are the available configuration values:

    colors	Turn on/off use of colors on zone stripes
    fontsize	The size of the font in the output window
    modify	Turn on/off execution of rollerd modification commands
    shading	Turn on/off shading of the status columns
    showskip	Turn on/off display of skipped zones
    skipcolor	The background color used for skipped zones

The rc.blinkenlights file is only searched for in the directory in which
blinkenlights is executed.  The potential problems inherent in this may cause
these blinkenlights-specific configuration files to be removed in the future.

This file is in the \"field value\" format, where field specifies the output
aspect and value defines the value for that field.  The following are the
recognized fields:

Empty lines and comments are ignored.  Comment lines are lines that start with
an octothorpe ('#').

Spaces are not allowed in the configuration values.

Choose your skipcolors carefully.  The only foreground color used is black, so
your background colors must work well with black.

WARNINGS

blinkenlights has several potential problems that must be taken into account:

	* development environment

	  blinkenlights was developed and tested on a single-user system
	  running X11.  While it works fine in this environment, it has not
	  been run on a system with many users or in a situation where the
	  system console hasn't been in use by the blinkenlights user.

	* long-term performance issues

	  In early tests, the longer blinkenlights runs, the slower the
	  updates become.  This is probably a result of the Tk implementation
	  or the way Tk interfaces with X11.  This is pure supposition, though.

	  This performance impact is affected by a number of things, such as
	  the number of zones managed by rollerd and the length of rollerd's
	  sleep interval.  Large numbers of zones or very short sleep
	  intervals will increase the possibility of blinkenlights'
	  performance degrading.

	  This appears to have been resolved by periodically performing a
	  complete rebuild of the screen.  blinkenlights keeps track of the
	  number of screen updates it makes and rebuilds the screen when this
	  count exceeds a threshold.  The threshold is built into blinkenlights
	  and stored in the $PAINTMAX variable.  This threshold may be adjusted
	  if there are too many screen rebuilds or if blinkenlights' performance
	  slows too much.  Raising the number will reduce the screen rebuilds;
	  lowering the number will (may) increase performance.

COPYRIGHT

	Copyright 2006-2010 SPARTA, Inc.  All rights reserved.
	See the COPYING file included with the DNSSEC-Tools package for details.

AUTHOR

	Wayne Morrison, tewok\@users.sourceforge.net

SEE ALSO

	rollctl(8), rollerd(8), zonesigner(8)

	blinkenlights.conf(5),
	Net::DNS::SEC::Tools::keyrec(5),
	Net::DNS::SEC::Tools::rollrec(5),
	Net::DNS::SEC::Tools::timetrans(5)


";

    ####################    end of help text    ####################

	#
	# Create a new dialog box to hold our help info.
	#
	$helpwin = $wm->Dialog(-title => "Help!",
			   -default_button => "Okay",
			   -buttons => ["Okay"]);

	#
	# Create a scrolled text widget for the help message itself.
	# 
	$helpwij = $helpwin->Scrolled("Text",
				      -scrollbars => 'e',
				      -state => 'normal');
	$helpwij->insert('1.0',$helpstr);
	$helpwij->configure(-state => 'disabled');
	$helpwij->pack(-side => 'top');

	#
	# Display our modal dialog.
	# 
	$helpwin->Show();
}

##############################################################################
#
# Option-based routines.
#
##############################################################################

#----------------------------------------------------------------------
# Routine:      version()
#
# Purpose:      Print the version number(s) and exit.
#
sub version
{
	print STDERR "$VERS\n";
	print STDERR "$DTVERS\n";
	exit(0);
}

#---------------------------------------------------------------------------
# Routine:	usage()
#
# Purpose:      Print a usage message and exit.
#
sub usage
{
	print STDERR "usage:  blinkenlights [-Version] <rollrec-file>\n";
	exit(0);
}

1;

#############################################################################
#
#	This text is also displayed in the help dialog.  Any changes to this
#	text should be reflected in that function.
#

=pod

=head1 NAME

blinkenlights - DNSSEC-Tools rollerd GUI

=head1 SYNOPSIS

  blinkenlights <rollrec-file>

=head1 DESCRIPTION

B<blinkenlights> is a GUI tool for use with monitoring and controlling the
DNSSEC-Tools B<rollerd> program.  It displays information on the current state
of the zones B<rollerd> is managing.  The user may control some aspects of
B<rollerd>'s execution using B<blinkenlights> menu commands.

B<blinkenlights> creates a window in which to display information about each
zone B<rollerd> is managing.  (These zones are those in B<rollerd>'s current
I<rollrec> file.)  As a zone's rollover status changes, B<blinkenlights> will
update its display for that zone.  Skipped zones, zones listed in the
I<rollrec> file but which are not in rollover or normal operation, are
displayed but have very little useful information to display.

The user may also select a set of zones to hide from the display.  These
zones, if in the rolling state, will continue to roll; however, their zone
information will not be displayed.  Display state for each zone will persist
across B<blinkenlights> executions.

Menu commands are available for controlling B<rollerd>.  The commands which
operate on a single zone may be executed by keyboard shortcuts.  The zone may
be selected either by clicking in its "zone stripe" or by choosing from a
dialog box.  Display and execution options for B<blinkenlights> are also
available through menu commands.  More information about the menu commands
is available in the MENU COMMANDS section.

B<blinkenlights> is only intended to be started by B<rollerd>, not directly
by a user.  There are two ways to have B<rollerd> start B<blinkenlights>.
First, B<rollctl> may be given the B<-display> option.  Second, the B<-display>
option may be given on B<rollerd>'s command line.

=head1 OPTIONS

B<blinkenlights> takes the following options:

=over 4

=item B<-display>

Tells B<rollerd> to execute B<blinkenlights> as a child process.

=item B<-Version>

Displays the version information for B<blinkenlights> and the DNSSEC-Tools
package.

=item B<-help>

Displays a usage message and exits.

=back

=head1 SCREEN LAYOUT

The B<blinkenlights> window is laid out as a series of "stripes".  The top
stripe contains status information about B<rollerd>, the second stripe
contains column headers, and the bulk of the window consists of zone stripes.
The list below provides more detail on the contents of each stripe.

See the WINDOW COLORS section for a discussion of the colors used for the
zone stripes.

=over 4

=item *

B<rollerd> information stripe

The information stripe contains four pieces of information:  B<rollerd>'s
current I<rollrec> file, the count of rolling zones, the count of skipped
zones, and the amount of time B<rollerd> waits between processing its queue.
Coincidentally, that last datum is also the amount of time between
B<blinkenlights> screen updates.

=item *

column headers stripe

This stripe contains the column headers for the columns of each zone stripe.

=item *

zone stripes

Each zone managed by B<rollerd> (i.e., every zone in the current I<rollrec>
file) will have a zone stripe which describes that zone's current state.  The
stripe is divided into four sections:  the zone name, the current rollover
state, and the zone's DNSSEC keys.

The zone name section just contains the name of the zone.

The rollover state section contains the rollover phase number, a text
explanation of the phase, and the amount of time remaining in that rollover
phase.  The phase explanation is "normal operation" when the zone isn't
currently in rollover.

The DNSSEC key section contains two subsections, one for the zone's ZSK keys
and another for the zone's KSK keys.  Each subsection contains the names of
the signing sets active for the zone.  The ZSK subsection lists the Current,
Published, and New ZSK keys; the KSK subsection lists the Current and
Published.

See the WINDOW COLORS section for a discussion of the colors used for the
zone stripes.

=back

=head1 WINDOW COLORS

The default B<blinkenlights> configuration uses window coloring to provide
visual cues and to aid in easily distinguishing zone information.  The default
window coloring behavior gives each zone stripe has its own color and the
rollover state section of each zone stripe is shaded to show the zone's phase.
Window coloring can be turned off (and on) with configuration options and menu
commands.

=head2 Color Usage

The two window coloring behaviors are discussed more fully below:

=over 4

=item *

zone stripe colors

Each rolling zone's stripe is given one of three colors:  blue, red, or green.
The color is assigned on a top-down basis and the colors wrap if there are
more than three zones.  So, the first zone is always blue, the second zone
red, the third zone green, the fourth zone blue, etc.

The colors do not stay with a particular zone.  If a rolling zone becomes a
skipped zone, the zone stripes will be reassigned new colors to account for
that skipped zone.

Skipped zones are not colored with these three colors.  Stripes for skipped
zones are colored either grey or a color set in the configuration file.  If
you choose to use a non-standard color for skipped zones your should ensure
that it is B<not> one of the colors used for rolling zones' stripes.
Modifying the B<skipcolor> configuration field allows the skipped-zone color
to be changing.

The B<colors> configuration field can be used to turn on or off the use of
colors for zone stripes.  If stripe coloring is turned off, then every stripe
will be displayed using the B<skipcolor> color.

=item *

rollover-state shading

The only portion of a zone stripe that changes color is the status column; the
color of the rest of the zone stripe stays constant.  Before a zone enters
rollover, the status column is the same color as the rest of the stripe.  When
the zone enters rollover, the status column's color is changed to a very light
shade of the stripe's normal color.  As the rollover phases progress towards
rollover completion, the status column's shade darkens.  Once rollover
completes, the status column returns again to the same shade as the rest of
that stripe.

The B<shading> configuration field can be used to turn on or off the use of
shading in the rollover-state column.  If shading is turned off, then the zone
stripe will be a solid color.

See the CONFIGURATION FILE section for information on setting the
configuration fields.

=back

=head2 Colors Used

The color names are taken from the X11 B<rgb.txt> file (X11 1.1.3 - XFree86
4.4.0 for MacOS X.)  If these aren't available in your B<rgb.txt> file,
similar names should be selected.  The actual red/green/blue values used are
given below to assist in finding suitable replacements.  These values were
taken from the B<rgb.txt> file.

Blue Shades:

    blue                0   0 255
    lightblue2        178 223 238
    darkslategray1    151 255 255
    skyblue1          135 206 255
    steelblue1         99 184 255
    turquoise1          0 245 255
    cornflower blue   100 149 237
    dodger blue        30 144 255

Red Shades:

    red               255   0   0
    pink              255 192 203
    lightsalmon1      255 160 122
    tomato            255  99  71
    indianred         205  92  92
    violetred1        255  62 150
    orangered1        255  69   0
    firebrick1        255  48  48

Green Shades:

    green               0 255   0
    darkseagreen1     193 255 193
    darkolivegreen1   202 255 112
    lightgreen        144 238 144
    seagreen1          84 255 159
    spring green        0 255 127
    greenyellow       173 255  47
    lawngreen         124 252   0

=head1 MENU COMMANDS

A number of menu commands are available to control the behavior of
B<blinkenlights> and to send commands to B<rollerd>.  These commands
are discusses in this section.

=head2 File Menu

The commands in this menu are basic GUI commands.

=over 4

=item *

Quit

B<blinkenlights> will stop execution.

=back

=head2 Options Menu

The commands in this menu control the appearance and behavior of
B<blinkenlights>.

=over 4

=item *

Row Colors (toggle)

This menu item is a toggle to turn on or off the coloring of zone stripes.
If row coloring is turned off, zone stripes will all be the same color.
If row coloring is turned on, zone stripes will be displayed in varying
colors.  See the WINDOW COLORS section for a discussion of row coloring.

=item *

Status Column Shading (toggle)

This menu item is a toggle to turn on or off the shading of the zone status
column.  If shading is turned off, the zone stripes will present a solid,
unchanging band of color for each zone.  If shading is turned on, the color
of the zone status column will change according to the zone's rollover state.

=item *

Skipped Zones Display (toggle)

This menu item is a toggle to turn on or off the display of skipped zones.  If
display is turned off, zone stripes for skipped zones will not be displayed.
If display is turned on, zone stripes for all zones will be displayed.

=item *

Modification Commands (toggle)

In some situations, it may be desirable to turn off B<blinkenlights>' ability
to send commands to B<rollerd>.  This menu item is a toggle to turn on or off
this ability.  If the commands are turned off, then the "Zone Control" menu
and keyboard shortcuts are disabled.  If the commands are turned on, then the
"Zone Control" menu and keyboard shortcuts are enabled.

=item *

Font Size

This menu item allows selection of font size of text displayed in the main
window.

Normally, changing the font size causes the window to grow and shrink as
required.  However, on Mac OS X there seems to be a problem when the size
selected increases the window size to be greater than will fit on the screen.
If the font size is subsequently reduced, the window size does not shrink in
response.

=back

=head2 General Control Menu

The commands in this menu are GUI interfaces for the B<rollctl> commands
related to I<general> zone management.

=over 4

=item *

Run the Queue

B<rollerd> is awoken and runs through its queue of zones.  The operation
required for each zone is then performed.

=item *

Skip Selected Zone

The selected zone will be moved to the skipped state.  This only has an effect
on rolling zones.  A zone may be selected by clicking on its zone stripe.  If
this command is selected without a zone having been selected, a dialog box is
displayed from which a currently rolling zone may be chosen.

=item *

Skip All Zones

All zones will be moved to the skipped state.  This has no effect on
currently skipped zones.

=item *

Halt Rollerd

B<rollerd>'s execution is halted.  As a result, B<blinkenlights>' execution
will also be halted.

=back

=head2 ZSK Control Menu

The commands in this menu are GUI interfaces for the B<rollctl> commands
related to ZSK-specific zone management.

=over 4

=item *

Roll Selected Zone's ZSK

The selected zone's ZSK will be moved to the rollover state.  This only has an
effect on skipped zones.  A zone may be selected by clicking on its zone
stripe.  If this command is selected without a zone having been selected, a
dialog box is displayed from which a currently skipped zone may be chosen.

=item *

Roll All Zones' ZSK

All zones' ZSKs will be moved to the rollover state.  This has no effect on
currently rolling zones.

=back

=head2 KSK Control Menu

The commands in this menu are GUI interfaces for the B<rollctl> commands
related to KSK-specific zone management.

=over 4

=item *

DS Published Selected Zone

This command is used to indicate that the selected zone's parent has published
a new DS record for the zone.  It moves the zone from phase 6 to phase 7 of
KSK rollover.

=item *

DS Published All Zones

This command is used to indicate that all the zones in KSK rollover phase 6
have new DS records published by their parents.  It moves all these zones from
phase 6 to phase 7 of KSK rollover.

=back

=head2 Zone Display Menu

The commands in this menu are GUI interfaces parts of the zone display.  There
are commands for displaying and hiding both zone stripes and key columns.  The
commands allow all, some, or none of the zone stripes and key columns to be
displayed.  Undisplayed rolling zones will continue to roll, but they will do
so without the B<blinkenlights> window indicating this.

=over 4

=item *

Zone Selection

A dialog box is created that holds a list of the zones currently managed by
B<rollerd>.  The user may select which zones should be displayed by clicking
on the zone's checkbox.  Zones with a selected checkbox will be displayed;
zones without a selected checkbox will not be displayed.

=item *

Display All Zones

All zones will be displayed in the B<blinkenlights> window.

=item *

Hide All Zones

No zones will be displayed in the B<blinkenlights> window.

=item *

KSK Sets (toggle)

This menu item is a toggle to turn on or off the display of KSK signing set
names.  If display is turned off, the columns holding the KSK signing set
names and labels will be removed from the display and the display window will
shrink.  If display is turned on, the columns holding the KSK signing set
names and labels will be restored to the display and the display window will
be expanded.

When displayed, KSK signing sets will always be the right-most columns.

=item *

ZSK Sets (toggle)

This menu item is a toggle to turn on or off the display of ZSK signing set
names.  If display is turned off, the columns holding the ZSK signing set
names and labels will be removed from the display and the display window will
shrink.  If display is turned on, the columns holding the ZSK signing set
names and labels will be restored to the display and the display window will
be expanded.

When displayed, ZSK signing sets will always be immediately to the right of
the zone status column.

=item *

Hide All Keysets

Turns off display of the KSK and ZSK signing set names.

=item *

Show All Keysets

Turns on display of the KSK and ZSK signing set names.

=back

=head2 Help Menu

The commands in this menu provide assistance to the user.

=over 4

=item *

Help

Display a window containing help information.

=back

=head1 CONFIGURATION FILE

Several aspects of B<blinkenlights>' behavior may be controlled from
configuration files.  Configuration value may be specified in the DNSSEC Tools
configuration file or in a more specific B<rc.blinkenlights>.  The system-wide
B<blinkenlights> configuration file is in the DNSSEC-Tools configuration
directory and is named B<blinkenlights.conf>.  Multiple B<rc.blinkenlights>
files may exist on a system, but only the one in the directory in which
B<blinkenlights> is executed is used.

The following are the available configuration values:

    colors	Turn on/off use of colors on zone stripes
    fontsize	The size of the font in the output window
    modify	Turn on/off execution of rollerd modification commands
    shading	Turn on/off shading of the status columns
    showskip	Turn on/off display of skipped zones
    skipcolor	The background color used for skipped zones

The B<rc.blinkenlights> file is B<only> searched for in the directory in
which B<blinkenlights> is executed.  The potential problems inherent in
this may cause these B<blinkenlights>-specific configuration files to be
removed in the future.

This file is in the "field value" format, where I<field> specifies the output
aspect and I<value> defines the value for that field.  The following are the
recognized fields:

Empty lines and comments are ignored.  Comment lines are lines that start
with an octothorpe ('#').

Spaces are not allowed in the configuration values.

Choose your skipcolors carefully.  The only foreground color used is black, so
your background colors must work well with black.

=head1 REQUIREMENTS

B<blinkenlights> is implemented in Perl/Tk, so both Perl and Perl/Tk must be
installed on your system.

=head1 WARNINGS

B<blinkenlights> has several potential problems that must be taken into
account.

=over 4

=item development environment

B<blinkenlights> was developed and tested on a single-user system running X11.
While it works fine in this environment, it has not been run on a system with
many users or in a situation where the system console hasn't been in use by
the B<blinkenlights> user.

=item long-term performance issues

In early tests, the longer B<blinkenlights> runs, the slower the
updates become.  This is I<probably> a result of the Tk implementation
or the way Tk interfaces with X11.  This is pure supposition, though.

This performance impact is affected by a number of things, such as the number
of zones managed by B<rollerd> and the length of B<rollerd>'s sleep interval.
Large numbers of zones or very short sleep intervals will increase the
possibility of B<blinkenlights>' performance degrading.

This appears to have been resolved by periodically performing a complete
rebuild of the screen.  B<blinkenlights> keeps track of the number of screen
updates it makes and rebuilds the screen when this count exceeds a threshold.
The threshold is built into B<blinkenlights> and stored in the B<$PAINTMAX>
variable.  This threshold may be adjusted if there are too many screen
rebuilds or if B<blinkenlights>' performance slows too much.  Raising the
number will reduce the screen rebuilds; lowering the number will (may)
increase performance.

=back

=head1 COPYRIGHT

Copyright 2006-2010 SPARTA, Inc.  All rights reserved.
See the COPYING file included with the DNSSEC-Tools package for details.

=head1 AUTHOR

Wayne Morrison, tewok@users.sourceforge.net

=head1 SEE ALSO

B<bubbles(8)>,
B<rollctl(8)>,
B<rollerd(8)>,
B<zonesigner(8)>

B<Net::DNS::SEC::Tools::timetrans(3)>

B<Net::DNS::SEC::Tools::keyrec(5)>,
B<Net::DNS::SEC::Tools::rollrec(5)>,

=cut

