#!/usr/bin/perl -w
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# Notes:
# I refer to individual qemu command configurations as 'vm' 
# configurations.
# 

use strict;
use Gtk2;
use Gtk2::GladeXML;
use Gnome2;
use Data::Dumper;
use IO::File;
use Locale::gettext;
use POSIX;

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

my $version = "1.3";
#my $glade_files_dir = 'glade'; #FOR TESTING
my $glade_files_dir = '/usr/share/qemu-launcher/glade';
my $application_name = 'Qemu Launcher';

my $conf_dir = $ENV{'HOME'}.'/.qemu-launcher';
#name of the main config file
my $conf_file = 'configuration';
#name of the defaults item in the configs list
my $defaults_name = 'default settings';

#Default settings for the defaults item
my %default_config = (
	'stopped'	=>	0,
	'statefile'	=> '',
	'loadstate'	=> 0,
	'args'		=>	'',
	'comment'	=> 'These defaults can be modified and used as a base for new configs.',
	'boot'		=> 'c',
	'cdrom' 	=>	'/dev/cdrom',
	'usecdrom'	=>	FALSE,
	'hda' 		=>	'',
	'hdb' 		=>	'',
	'hdc' 		=>	'',
	'hdd' 		=>	'',
	'fda' 		=>	'',
	'fdb' 		=>	'',
	'initrd' 	=> 	'',
	'kernel' 	=> 	'',
	'tunscript' => 	'',
	'audio' 	=>	FALSE,
	'linuxboot' => 	FALSE,
	'localtime' => 	TRUE,
	'nogfx' 	=>	FALSE,
	'fullscreen' => FALSE,
	'snapshot' 	=> 	FALSE,
	'ifmac' 	=>	'',
	'kernelcmd' => 	'',
	'gfxtype' 	=> 	'pcivga', #  pcivga or vga
	'bustype' 	=> 	'pci', #isa or pci
	'nettype' 	=> 	'usermode', # opentun,dummy,tuntap, usermode
	'smbdir' => '',
	'numnics' 	=> 	1,
	'portredirects'	=>	'',
	'ram' 		=>	128,
	'tunfd' 	=>	0,
	'log' 	=> 	FALSE,
	'logthese' 	=> 	'', # cpu,exec,in_asm,int,ioport,op_opt,op,out_asm,pcall
	'priority'	=> 0
);

my %main_config_defaults = (
		'imagedir' => $ENV{'HOME'},
		'qemupath' => '/usr/bin/qemu',
		'prelaunchcommand'	=> ''
	);

#some hashes to make lookups easier
my %boot_values = ( 	'Floppy A'	=> 'a',	'Floppy B'	=> 'b',	'Hard Disk 0'	=> 'c',	'CDROM' 	=> 'd');
my %boot_values_num = ('a'=>0,'b'=>1,'c'=>2,'d'=>3);
my %boot_values_dev =  ( 	'a'	=> 'fda',	'b'	=> 'fdb',	'c'	=> 'hda',	'd' 	=> 'cd');
		
# what a valid vm name should look like
my $good_name_regex = '^[a-zA-Z0-9_ -]+$';

my $main_config; # qemu-launcher config
my $gladexml; #main glade xml file
my @vm_list; #stores the list of qemu configs(VM's)
my $loaded_vm_config; #name of current config
my $quick_launch = FALSE; # TRUE if user has passed an argument(name of vm)
	
###########################
#
# functions for maniplulating the vm configs
#
###########################
	
# write given vm config to a file
# args: filename, config to write(hashref)
sub save_vm_config_to_file{
	my ($name, $vm_config) = @_;
	print "save_vm_config_to_file(): Saving config:\n".Dumper($vm_config) if DEBUG_LEVEL > 1;

	my $fh = new IO::File "$conf_dir/$name",'w' ;
	if( ! defined $fh ){	
		alert_user( gettext(sprintf("Could not save configuration for %s.",$name)));
		return FALSE;
	}
	foreach my $val ( keys %{$vm_config} ){
		if( $val eq 'portredirects' and $vm_config->{$val} ){
			my @redirs;
			foreach my $redir ( @{$vm_config->{$val}} ){
				$redir =~ s/:/,/g;
				push @redirs, $redir;
			}
			my $redirs_string = join ' ',@redirs;
			print $fh "portredirects:$redirs_string\n";
		}else{
			print $fh "$val:";
			print $fh $vm_config->{$val} if defined $vm_config->{$val};
			print $fh "\n";
		}
	}
	close ($fh);
	return TRUE;
}

# load a vm config file and return a config hash
# args: file name
sub load_vm_config{
	my $name = shift;
	my $fh;
	my $filename = "$conf_dir/$name";
	my %config = %default_config; 
	$fh = new IO::File $filename,'r' ;
	if( ! defined $fh ){	
		alert_user( gettext(sprintf("Could not read configuration for %s.",$name)));
		return FALSE;
	}
	while(<$fh>){
		next if /^#/;
		if( /\s*([^:]+):(.*)$/ ){
			if( $1 eq 'portredirects'){
				my @redirs = split ' ',$2;
				foreach my $redir ( @redirs ){
					$redir =~ s/,/:/g;
				}
				$config{'portredirects'} = \@redirs;
			}else{
				$config{$1} = $2;
			}
		}
	}
	close( $fh );
	$loaded_vm_config = $name;
	print "load_vm_config(): Loaded config:\n".Dumper(\%config)  if DEBUG_LEVEL > 1;
	return \%config;
}

# delete a vm config file, with ack dialog
# args: none
sub delete_vm_config {
	my $name = get_comboentry_val('vmnamecomboboxentry');

	if( ask_user("\nDelete '$name' ?\n") ){	
		if( unlink "$conf_dir/$name" ){
			my $widget = $gladexml->get_widget('vmnamecomboboxentry');
			my $pos = $widget->get_active() ;
			$widget->remove_text($pos);
		}else{
			alert_user( gettext(sprintf("<b>Error:</b>\nCould not delete %s:\n", $name)).$!);
		}
	}
}

# get a list of all config files in the .qemulauncher dir, except the main config
# args: none
sub list_vm_configs{
	my $dirname;
	my @names = ();
	if( ! -d $conf_dir ){
		return FALSE;
	}
	opendir( CFGDIR,  $conf_dir) 
		or return FALSE;

	while( $dirname = readdir(CFGDIR)){
		next if $dirname =~ /^\./;
		next if $dirname eq $conf_file;
		next if $dirname eq $defaults_name;
		push( @names, $dirname);
	}
	closedir( CFGDIR );
	@names = sort(@names);
	unshift( @names,$defaults_name);
	return @names;	
}

# Set the GUI vm config values to what is set in the given config hash
# args: hashref to a vm config
sub push_vm_config{
	my $config = shift;

	($gladexml->get_widget('snapshotcheckbutton'))->set_active( $config->{'snapshot'} );

	($gladexml->get_widget('argsentry'))->set_text($config->{'args'});
	($gladexml->get_widget('stoppedcheckbutton'))->set_active( $config->{'stopped'} );
	($gladexml->get_widget('statefileentry'))->set_filename( $config->{'statefile'});
	($gladexml->get_widget('loadstatecheckbutton'))->set_active( $config->{'loadstate'} );

	($gladexml->get_widget('commententry'))->set_text($config->{'comment'});

	($gladexml->get_widget('linuxbootcheckbutton'))->set_active( $config->{'linuxboot'} );
	($gladexml->get_widget('kernelfileentry'))->set_filename( $config->{'kernel'});
	($gladexml->get_widget('initrdfileentry'))->set_filename( $config->{'initrd'});
	($gladexml->get_widget('kernelcmdentry'))->set_text($config->{'kernelcmd'});

	($gladexml->get_widget('bootcombobox'))->set_active( $boot_values_num{$config->{'boot'}} );
	
	($gladexml->get_widget('fdafileentry'))->set_filename( $config->{'fda'});
	($gladexml->get_widget('fdbfileentry'))->set_filename( $config->{'fdb'});
	($gladexml->get_widget('hdafileentry'))->set_filename( $config->{'hda'});
	($gladexml->get_widget('hdbfileentry'))->set_filename( $config->{'hdb'});
	($gladexml->get_widget('hdcfileentry'))->set_filename( $config->{'hdc'});
	($gladexml->get_widget('hddfileentry'))->set_filename( $config->{'hdd'});
	($gladexml->get_widget('cdfileentry'))->set_filename( $config->{'cdrom'});

	($gladexml->get_widget('usecdromcheckbutton'))->set_active( $config->{'usecdrom'} );	

	($gladexml->get_widget('ramspinbutton'))->set_value($config->{'ram'} );
	
	($gladexml->get_widget('numnicspinbutton'))->set_value($config->{'numnics'});
	($gladexml->get_widget('ifmacentry'))->set_text($config->{'ifmac'});
	($gladexml->get_widget('tunscriptfileentry'))->set_filename($config->{'tunscript'});
	($gladexml->get_widget('tunfdspinbutton'))->set_value($config->{'tunfd'});
	($gladexml->get_widget('smbdirfileentry'))->set_filename($config->{'smbdir'});
	
	if( $config->{'nettype'} eq 'usermode'  ){
		($gladexml->get_widget('usermoderadiobutton'))->set_active(TRUE) ;
	}elsif( $config->{'nettype'} eq 'tuntap'  ){
		($gladexml->get_widget('tuntapradiobutton'))->set_active(TRUE) ;
	}elsif( $config->{'nettype'} eq 'opentun'  ){
		($gladexml->get_widget('opentunradiobutton'))->set_active(TRUE) ;
	}elsif( $config->{'nettype'} eq 'dummy'  ){
		($gladexml->get_widget('dummyradiobutton'))->set_active(TRUE) ;
	}
	
	my $redirs=[];
	if( $config->{'portredirects'} ){
		$redirs = $config->{'portredirects'};
	}
	push_redirs($redirs);
	
	($gladexml->get_widget('localtimecheckbutton'))->set_active($config->{'localtime'} || 0 );
	($gladexml->get_widget('audiocheckbutton'))->set_active($config->{'audio'} || 0);
	($gladexml->get_widget('nogfxcheckbutton'))->set_active($config->{'nogfx'} || 0);
	($gladexml->get_widget('fullscreencheckbutton'))->set_active($config->{'fullscreen'} || 0);
	
	if( $config->{'bustype'} eq 'pci' ){
		($gladexml->get_widget('pciradiobutton'))->set_active(TRUE) ;
	}elsif( $config->{'bustype'} eq 'isa'  ){
		($gladexml->get_widget('isaradiobutton'))->set_active(TRUE);
	}
	
	if( $config->{'gfxtype'} eq 'pcivga' ){
		($gladexml->get_widget('gfxpciradiobutton'))->set_active(TRUE);
	}elsif( $config->{'gfxtype'} eq 'vga' ){
		($gladexml->get_widget('gfxvgaradiobutton'))->set_active(TRUE);
	}
	
	($gladexml->get_widget('logcheckbutton'))->set_active($config->{'log'} || 0);

	my @logitems = split ',',$config->{'logthese'};
	foreach my $logtype ( @logitems ){
		($gladexml->get_widget( $logtype.'togglebutton' ))->set_active(TRUE);
	}
	
	($gladexml->get_widget('prispinbutton'))->set_value($config->{'priority'} );
	
}

# get the current config from the GUI and return it
# args: none
sub pull_vm_config {
	my %config;

	$config{'snapshot'} = ($gladexml->get_widget('snapshotcheckbutton'))->get_active() || 0;
	
	$config{'args'} = ($gladexml->get_widget('argsentry'))->get_text();
	$config{'stopped'} = ($gladexml->get_widget('stoppedcheckbutton'))->get_active() || 0;
	$config{'statefile'} = ($gladexml->get_widget('statefileentry'))->get_full_path(TRUE);
	$config{'loadstate'} = ($gladexml->get_widget('loadstatecheckbutton'))->get_active() || 0;

	$config{'comment'} = ($gladexml->get_widget('commententry'))->get_text();

	$config{'linuxboot'} = ($gladexml->get_widget('linuxbootcheckbutton'))->get_active() || 0;
	$config{'kernel'} = ($gladexml->get_widget('kernelfileentry'))->get_full_path(TRUE);
	$config{'initrd'} = ($gladexml->get_widget('initrdfileentry'))->get_full_path(TRUE);
	$config{'kernelcmd'} = ($gladexml->get_widget('kernelcmdentry'))->get_text();

	my $bootcombobox = $gladexml->get_widget('bootcombobox');
	$config{'boot'}  = $boot_values{($bootcombobox->get_model)->get($bootcombobox->get_active_iter(), 0)};
	
	$config{'fda'} = ($gladexml->get_widget('fdafileentry'))->get_full_path(TRUE);
	$config{'fdb'} = ($gladexml->get_widget('fdbfileentry'))->get_full_path(TRUE);
	$config{'hda'} = ($gladexml->get_widget('hdafileentry'))->get_full_path(TRUE);
	$config{'hdb'} = ($gladexml->get_widget('hdbfileentry'))->get_full_path(TRUE);
	$config{'hdc'} = ($gladexml->get_widget('hdcfileentry'))->get_full_path(TRUE);
	$config{'hdd'} = ($gladexml->get_widget('hddfileentry'))->get_full_path(TRUE);
	$config{'cdrom'} = ($gladexml->get_widget('cdfileentry'))->get_full_path(TRUE);

	$config{'usecdrom'} = ($gladexml->get_widget('usecdromcheckbutton'))->get_active() || 0;
	
	$config{'ram'} = ($gladexml->get_widget('ramspinbutton'))->get_value();
	
	$config{'numnics'} = ($gladexml->get_widget('numnicspinbutton'))->get_value();
	$config{'ifmac'} = ($gladexml->get_widget('ifmacentry'))->get_text();
	$config{'tunscript'} = ($gladexml->get_widget('tunscriptfileentry'))->get_full_path(TRUE);
	$config{'tunfd'} = ($gladexml->get_widget('tunfdspinbutton'))->get_value();
	$config{'smbdir'} = ($gladexml->get_widget('smbdirfileentry'))->get_full_path(TRUE);
	
	if(  ($gladexml->get_widget('usermoderadiobutton'))->get_active() ){
		$config{'nettype'} = 'usermode';
	}elsif(  ($gladexml->get_widget('tuntapradiobutton'))->get_active() ){
		$config{'nettype'} = 'tuntap';
	}elsif(  ($gladexml->get_widget('opentunradiobutton'))->get_active() ){
		$config{'nettype'} = 'opentun';
	}elsif(  ($gladexml->get_widget('dummyradiobutton'))->get_active() ){
		$config{'nettype'} = 'dummy';
	}
	
	$config{'localtime'} = ($gladexml->get_widget('localtimecheckbutton'))->get_active() || 0;
	$config{'audio'} = ($gladexml->get_widget('audiocheckbutton'))->get_active() || 0;
	$config{'nogfx'} = ($gladexml->get_widget('nogfxcheckbutton'))->get_active() || 0;
	$config{'fullscreen'} = ($gladexml->get_widget('fullscreencheckbutton'))->get_active() || 0;
	
	if(  ($gladexml->get_widget('pciradiobutton'))->get_active() ){
		$config{'bustype'} = 'pci';
	}elsif(  ($gladexml->get_widget('isaradiobutton'))->get_active() ){
		$config{'bustype'} = 'isa';
	}
	
	if(  ($gladexml->get_widget('gfxpciradiobutton'))->get_active() ){
		$config{'gfxtype'} = 'pcivga';
	}elsif(  ($gladexml->get_widget('gfxvgaradiobutton'))->get_active() ){
		$config{'gfxtype'} = 'vga';
	}
	
	$config{'log'} = ($gladexml->get_widget('logcheckbutton'))->get_active() || 0;
	my @logitems = ();
	foreach my $logtype ( qw/cpu exec in_asm int ioport op_opt op out_asm pcall/ ){
		push(@logitems,$logtype) if( ($gladexml->get_widget( $logtype.'togglebutton' ))->get_active() )
	}
	$config{'logthese'} = join(',',@logitems);
	
	$config{'priority'} = ($gladexml->get_widget('prispinbutton'))->get_value();
	
	my @redirs = pull_redirs();
	$config{'portredirects'} = \@redirs;
	
	print "pull_vm_config(): Pulled config:\n".Dumper(\%config) if DEBUG_LEVEL > 1;
	return \%config;
}

#callback to do checks before actually saving
# args: none
sub save_vm_config{
	my $name = get_comboentry_val('vmnamecomboboxentry');
	my $config = pull_vm_config();
	print "save_vm_config(): Pulled config:\n".Dumper($config) if DEBUG_LEVEL > 1;
	if( $name eq ''){
		alert_user( gettext("Please enter a name for this configuration."));
	}
	if( check_vm_name($name) ){
		save_vm_config_to_file( $name, $config);
		if( $loaded_vm_config ne $name and ! in_vm_list($name) ){
				add_vm_to_list($name);
				$loaded_vm_config = $name;
		}
	}
	return TRUE;
}

# callback for when vm list entry is selected, if name matches one in list, get config
# args: none
sub show_vm_config{
	my $widget = $gladexml->get_widget('vmnamecomboboxentry');
	my $model = $widget->get_model();
	my $iter = $widget->get_active_iter();
	if( defined $iter ){
		my $name = $model->get($iter, $widget->get_text_column());
		my $config = load_vm_config( $name );
		push_vm_config($config);
	}
}



############################
#
#  Functions for manipulating the main config
#
###########################

# Read main config from file
# args: none
sub get_main_config{
	my $fh;
	my $filename = $conf_dir.'/'.$conf_file;
	my $new_config = {};

	$fh = new IO::File $filename,'r' ;
	if( ! defined $fh ){	
		alert_user( gettext(sprintf("Could not read configuration from %s",$filename)));
		return FALSE;
	}
	while(<$fh>){
		next if /^#/;
		if( /\s*([^:]+):(.*)$/ ){
			$new_config->{$1} = $2;
		}
	}
	close( $fh );
	print "get_main_config(): got:\n".Dumper($new_config) if DEBUG_LEVEL > 1;
	$main_config = $new_config;
	return TRUE;
}

# set widgets to main config settings
# args: none
sub set_main_config{
	($gladexml->get_widget('imagedirfileentry'))->set_filename( $main_config->{'imagedir'}) ;
	($gladexml->get_widget('qemupathfileentry'))->set_filename( $main_config->{'qemupath'}) ;
	($gladexml->get_widget('prelaunchentry'))->set_text($main_config->{'prelaunchcommand'});
}


# save main config to file
# args: hashref to a config
sub save_main_config{
	my $fh;
	my $filename = "$conf_dir/$conf_file";
	my $main_config = shift;
		
	if( ! -d $conf_dir ){
		mkdir( $conf_dir );
	}
	
	$fh = new IO::File $filename,'w' ;
	if( ! defined $fh ){	
		alert_user( gettext(sprintf("Could not save configuration to %s",$filename)));
		return FALSE;
	}
	foreach my $var ( keys %{$main_config} ){
		print $fh "$var:";
		print $fh $main_config->{$var} if defined $main_config->{$var};
		print $fh "\n";
	}
	close( $fh );
	return TRUE;
}


# Get values from widgets, check values, and store in main config
# args: none
sub apply_main_config{
	my $error=FALSE;
	
	my $imagedir = ($gladexml->get_widget('imagedirfileentry'))->get_full_path (FALSE);
	my $qemupath =  ($gladexml->get_widget('qemupathfileentry'))->get_full_path (FALSE);

	$main_config->{'prelaunchcommand'} = ($gladexml->get_widget('prelaunchentry'))->get_text();
	
	if( ! $imagedir ){
		alert_user( gettext("Please enter a valid data directory. ").  gettext("Settings not applied."));
		$error=TRUE;
	}elsif( ! -d $imagedir ){
		alert_user(gettext(sprintf("%s does not exist.", $imagedir)).gettext("Settings not applied."));
		$error=TRUE;
	}else{
		$main_config->{'imagedir'} = $imagedir;
	}
	if( $qemupath and ! -f $qemupath ){
		alert_user(gettext(sprintf("%s does not exist.", $imagedir)).gettext("Settings not applied."));
		$error=TRUE;
	}else{
		$main_config->{'qemupath'} = $qemupath;
	}	
}


# callback for save button.
# args: none
sub cb_save_config{
	apply_main_config();
	save_main_config($main_config);
}

###########################
#
# Big important subs
#
###########################

# Run Qemu possibly using nice to set the priority
# args: vm config to us otherwise uses GUI settings
sub run_vm {	
	my $qcmd;
	my @parts;
	my $config;
	if( defined $_[0] ){
		$config = shift;
	}else{
		$config = pull_vm_config();
		return FALSE if ! check_config( $config );
	}
	
	push @parts,"nice -n".$config->{'priority'}." " if( $config->{'priority'} !=0 );

	push @parts, $main_config->{'qemupath'};

	push @parts,'-S' if( $config->{'stopped'} );

	if( $config->{'loadstate'} and $config->{'statefile'} ) {
		push @parts,'-loadvm';
		push @parts, $config->{'statefile'} ;
	}
	
	if( $config->{'linuxboot'}  and $config->{'kernel'} ){
		push @parts,'-kernel';
		push @parts,$config->{'kernel'};
		if( $config->{'kernelcmd'} ){
			push @parts,'-append';
			push @parts,"'".$config->{'kernelcmd'}."'";
		}
		if( $config->{'initrd'} ){
			push @parts,'-initrd';
			my $tmp =$config->{'initrd'};
			push @parts,$tmp;
		}
	}
	if( $config->{'boot'} ) {
		push @parts,'-boot';
		push @parts, $config->{'boot'} ;
	}
	
	if( $config->{'snapshot'} ) {
		push @parts,'-snapshot';
	}
	if( $config->{'ram'} ) {
		push @parts,'-m';
		push @parts, $config->{'ram'} ;
	}
	
	foreach my $disk ( qw/fda fdb hda hdb hdd/ ){
		if( $config->{$disk} ) {
			push @parts,"-$disk";
			push @parts, $config->{$disk} ;
		}
	}	

	if( $config->{'usecdrom'} ){
		push @parts, "-cdrom ".$config->{'cdrom'} if $config->{'cdrom'};
	}elsif(  $config->{'hdc'} ){
		push @parts, "-hdc ".$config->{'hdc'} if $config->{'hdc'}
	}
	
	if( $config->{'numnics'} ) {
		push @parts,'-nics';
		push @parts, $config->{'numnics'} ;
	}
	
	if( $config->{'ifmac'} ) {
		push @parts,'-macaddr';
		push @parts, $config->{'ifmac'} ;
	}
	
	if( $config->{'nettype'} eq 'usermode' ) {
		push @parts,'-user-net';
	}
	if( $config->{'nettype'} eq 'dummy' ) {
		push @parts,'-dummy-net';
	}
	if( $config->{'nettype'} eq 'opentun' ) {
		push @parts,'-tun-fd';
		push @parts, $config->{'tunfd'} ;
	}
	if( $config->{'nettype'} eq 'tuntap' ) {
		if( $config->{'tunscript'} ){
			push @parts,'-n';
			push @parts, $config->{'tunscript'} ;
		}	
	}
	if( $config->{'smbdir'} ) {
		push @parts,'-smb';
		push @parts,$config->{'smbdir'};
	}
	if( $config->{'portredirects'} ){
		foreach my $redir ( @{$config->{'portredirects'}} ){
			push @parts,'-redir';
			push @parts,$redir;
		}
	}
	if( $config->{'localtime'}  ) {
		push @parts,'-localtime';
	}
	if( $config->{'audio'}  ) {
		push @parts,'-enable-audio';
	}
	if( $config->{'fullscreen'}  ) {
		push @parts,'-full-screen';
	}
	if( $config->{'nogfx'}  ) {
		push @parts,'-nographic';
	}else{
		if($config->{'gfxtype'} eq 'vga'){
			push @parts,'-std-vga' ;
		}
	}
	
	if( $config->{'bustype'} eq   'isa') {
		push @parts,'-isa';
	}
	
	if( $config->{'log'} and scalar( $config->{'logthese'} )){
		push @parts,'-d';
		push @parts, join ',',($config->{'logthese'}) ;
	}

	push @parts, $config->{'args'} if( $config->{'args'} );

	push @parts, '&' if ( ! $quick_launch );
	
	$qcmd = join(' ',@parts);
	print "$qcmd\n";		
	
	system($main_config->{'prelaunchcommand'}) if( ! $main_config->{'prelaunchcommand'} =~ /^\s*$/ );
	
	system( $qcmd );

}


# create the disk image dialog and setup all signals for handling events.
# Why do it all in one sub? well...
# args: drive name this image will be used for
sub create_disk_image_dialog {
	my $drive = shift;
	my $target_entry = $gladexml->get_widget($drive.'fileentry');
	my $dskimggladexml = Gtk2::GladeXML->new( $glade_files_dir.'/diskimages.glade');
	#$dskimggladexml->signal_autoconnect_from_package('main');

	#set dir fileentry to default from main config
	my $conf_img_dir = ($gladexml->get_widget('imagedirfileentry'))->get_full_path(TRUE);
	($dskimggladexml->get_widget('imgdirfileentry'))->set_filename($conf_img_dir);

	my $window = $dskimggladexml->get_widget('diskimageswindow');
	$window->signal_connect ('delete_event' => sub { my $self = shift; $self->hide; return TRUE; });
	
	my $dskimgcancelbutton = $dskimggladexml->get_widget('cancelbutton'); 
	$dskimgcancelbutton->signal_connect ('clicked' => sub { 
		$window->destroy;
		return TRUE;  
	});
	
	my $origimgfileentry = $dskimggladexml->get_widget('origimgfileentry');
	my $imgsizespinbutton = $dskimggladexml->get_widget('imgsizespinbutton');
	$origimgfileentry->set_sensitive(FALSE);
	$imgsizespinbutton->set_sensitive(TRUE);

	#($dskimggladexml->get_widget('newcoradiobutton'))->set_active(TRUE);

	($dskimggladexml->get_widget('flatradiobutton'))->signal_connect ('clicked' => sub { 
		$origimgfileentry->set_sensitive(FALSE);
		$imgsizespinbutton->set_sensitive(TRUE);
		return TRUE;  
	});
	($dskimggladexml->get_widget('newcowradiobutton'))->signal_connect ('clicked' => sub { 
		$origimgfileentry->set_sensitive(FALSE);
		$imgsizespinbutton->set_sensitive(TRUE);
		return TRUE;  
	});
	($dskimggladexml->get_widget('oldcowradiobutton'))->signal_connect ('clicked' => sub { 
		$origimgfileentry->set_sensitive(TRUE);
		$imgsizespinbutton->set_sensitive(FALSE);
		return TRUE;  
	});
	($dskimggladexml->get_widget('vmwareradiobutton'))->signal_connect ('clicked' => sub { 
		$origimgfileentry->set_sensitive(TRUE);
		$imgsizespinbutton->set_sensitive(FALSE);
		return TRUE;  
	});

	my $dskimggobutton = $dskimggladexml->get_widget('gobutton'); 
	$dskimggobutton->signal_connect ('clicked' => sub { 
		my $type;
		foreach my $radiobutton ( qw/flat newcow oldcow vmware/ ){
			$type = $radiobutton if ($dskimggladexml->get_widget( $radiobutton.'radiobutton' ))->get_active;
		}

		my $size = ($dskimggladexml->get_widget('imgsizespinbutton'))->get_value;
		my $dir = ($dskimggladexml->get_widget('imgdirfileentry'))->get_full_path(TRUE);
		my $name = ($dskimggladexml->get_widget('imgnameentry'))->get_text;
		my $orig = ($dskimggladexml->get_widget('origimgfileentry'))->get_full_path(TRUE);

		if( !$dir ){
			alert_user( gettext("Not a valid directory for 'New image location'."));
			return TRUE;
		}
		if( !$name){
			alert_user( gettext("Not a valid name for 'New image name'."));
			return TRUE;
		}
		if( !$orig and ( $type eq 'vmware' or $type eq 'oldcow' ) ){
			alert_user( gettext("Not a valid image for 'Original image'."));
			return TRUE;
		}

		my $error=FALSE;
		
		if( $type eq 'flat' ){
			if( system("qemu-img create -f raw \"$dir/$name\" ${size}M > /dev/null 2>&1") ){
				$error = TRUE;
			}
		}elsif( $type eq 'newcow' ){
			if( system("qemu-img create -f qcow \"$dir/$name\" ${size}M") ){
				$error = TRUE;
			}
		}elsif( $type eq 'oldcow' ){
			if(  system("qemu-img convert -f raw  \"$orig\" -O qcow \"$dir/$name\"") ){
				$error = TRUE;
			}
		}elsif( $type eq 'vmware' ){
			if( system("qemu-img convert -f vmdk  \"$orig\" -O qcow \"$dir/$name\"") ){
				$error = TRUE;
			}
		}
		if( $error ){
			alert_user( gettext("An error occurred creating the disk image:")."\n$!");
		}else{
			my $target_fileentry = $gladexml->get_widget( $drive.'fileentry'); 
			$target_fileentry->set_filename("$dir/$name");
			$window->destroy;
		}
		return TRUE;
	});

}

#Check the values in the given config, raise alerts and return FALSE if problems, else TRUE
#args: a config hashref
sub check_config {
	my $config = shift;
	my $names = { map {$boot_values{$_}, $_ } (keys %boot_values) };
	my $bootdev = $config->{'boot'};
	my $bootdevfileentry = $gladexml->get_widget( $boot_values_dev{ $bootdev }.'fileentry');
	my $bootdevpath = $bootdevfileentry->get_full_path(TRUE);
	
	if( ! defined $bootdevpath ){
		alert_user( gettext(sprintf("No file specified for %s.", $names->{$bootdev} )));
		return FALSE;
	}elsif( ! -e $bootdevpath ){
		alert_user( gettext(sprintf("Not a valid file for %s.", $names->{$bootdev} )));
		return FALSE;
	}
	
	return TRUE;
}

###########################
#
# Miscelaneous functions
#
###########################

#Check to see if a given name is currently in the vm list
# args: name of vm config
sub in_vm_list{
	my $vmname = shift;
	foreach my $entry ( @vm_list){ return TRUE if $entry eq $vmname; }
	return FALSE;
}

# add a name to the vm list combobox widget
# args: name to add
sub add_vm_to_list{
	my $vmname = shift;
	my $combo = $gladexml->get_widget('vmnamecomboboxentry');
	$combo->append_text($vmname);
}

# display a modal alert for the user 
# args: message to display
sub alert_user{
	my $msg = shift;
	print 'alert_user(): "'.$msg.'"'."\n" if DEBUG_LEVEL > 1;
	if( $quick_launch ){
		print $msg."\n";
	}else{
		my $dialog = $gladexml->get_widget('alertdialog'); 
		my $label = $gladexml->get_widget('alertdialogtext'); 
		$label->set_markup($msg);
		$dialog->signal_connect ('response' => sub { 
			my $self=shift; 
			$self->hide;
		});
		$dialog->run;
	}
}

# Ask the user an ok/cancel question
# args: message to display
sub ask_user{
	my $msg = shift;
	print 'ask_user(): "'.$msg.'"'."\n" if DEBUG_LEVEL > 1;
	my $dialog = $gladexml->get_widget('okcanceldialog'); 
	my $label = $gladexml->get_widget('okcanceldialogtext'); 
	$label->set_text($msg);
	$dialog->signal_connect ('response' => sub { 
		my $self=shift; 
		$self->hide;
	});
	return TRUE if $dialog->run eq 'ok';
}

# read the current value from a combobox.
# args: widget name
sub get_comboentry_val {
	my $val;
	my $widgetname = shift;
	my $widget = $gladexml->get_widget($widgetname);
	my $model = $widget->get_model();
	my $iter = $widget->get_active_iter();
	if( defined $iter ){
		$val = $model->get($iter, $widget->get_text_column());
	}else{
		$val = ($widget->get_child())->get_text();
	}
	return $val
}

# read the current value from a combobox.
# args: widget name
sub get_combobox_val {
	my $widgetname = shift;
	my $widget = $gladexml->get_widget($widgetname);
	my $model = $widget->get_model();
	my $iter = $widget->get_active_iter();
	my $val = $model->get($widget->get_active_iter(),0);
	return $val
}

# set a combobox value to be active
# args: $combobox, $value
sub set_combobox_val {
	my $cb = shift;
	my $val = shift;
	my $soon_to_be_active_iter;
	my $model = $cb->get_model();
	my $iter = $model->get_iter_first;
	do {
		my $cur_val = $model->get($iter,0);
		$soon_to_be_active_iter = $iter if( $cur_val eq $val);
	}while( !$soon_to_be_active_iter and $iter = $model->iter_next($iter) );
	$cb->set_active_iter($soon_to_be_active_iter) if($soon_to_be_active_iter);
}


# Check a string to see if we will allow it to be used as
# a vm name. If not, issue a warning to the user and 
# return FALSE.
# legal characters defined by: $good_name_regex
# args:
sub check_vm_name{
	my $name = shift;

	if( $name =~ /$good_name_regex/ ){
		return TRUE;
	}else{
		alert_user( gettext("Some characters in the name are not valid.\n Use only letters, numbers, -, _, and space."));
		return FALSE;
	}
}


# Add the redir rule to the lists of redirs and 
# return the new list.(skips if it exists already)
# args: $new_redir, \@redirs
sub add_redir {
	my $new_redir = $_[0];
	my @redirs = @{$_[1]};
	my $found;
	foreach my $redir ( @redirs ){
		if( $redir eq $new_redir ){
			$found = 1;
			last;
		}
	}
	push( @redirs, $new_redir) if ! $found;
	return @redirs;
}

# Changes $old_redir to $new_redir in the GUI
# Pulls list from GUI, makes change in list,
# returns new list.
# Args: $new_redir, $old_redir
sub change_redir {
	my $new_redir = $_[0];
	my $old_redir = $_[1];
	my @redirs = pull_redirs();
	my @new_redirs;
	foreach my $redir ( @redirs ){
		$redir = $new_redir if( $redir eq $old_redir );
		push( @new_redirs, $redir);
	}
	return @new_redirs;
}

# Removes $redir from redir rules list, returns
# new list.
# args: $redir, \@redirs
sub del_redir {
	my $del_redir = $_[0];
	my @redirs = @{$_[1]};
	my @new_redirs;
	foreach my $redir ( @redirs ){
		push( @new_redirs, $redir) if( $redir ne $del_redir );
	}
	return @new_redirs;
}

# Pulls redir rules from GUI and returns list
# args: none
sub pull_redirs {
	my $redirstreemodel = ($gladexml->get_widget('redirstreeview'))->get_model;
	my $iter = $redirstreemodel->get_iter_first;
	my @redirs;
	while( $iter ){ 
		my ( $proto, $hport, $gip, $gport) = $redirstreemodel->get($iter,(0..3));
		$iter = $redirstreemodel->iter_next($iter);
		push @redirs,"$proto:$hport:$gip:$gport";
	}
	return @redirs;
}

# Clear the redirs treeview and add the items from \@redirs
# args: \@redirs
sub push_redirs {
	my @redirs = @{$_[0]};
	my $redirstreemodel = ($gladexml->get_widget('redirstreeview'))->get_model;
	my $iter = $redirstreemodel->get_iter_first;
	if ( $iter ){
		while( $redirstreemodel->remove($iter) ){};
	}		
    $iter = $redirstreemodel->get_iter_first;
	foreach my $redir ( @redirs ){
		my $listiter = $redirstreemodel->append;
		my ( $proto, $hport, $gip, $gport) = split ':', $redir;
		$redirstreemodel->set ($listiter, 0, $proto,1, $hport, 2, $gip, 3, $gport);
	}
}

# Get the redir rule that is selected in the treeview
# returns 0 if none selected
#args: none
sub get_selected_redir {
	my $redirstreeview = $gladexml->get_widget('redirstreeview');
	my ($path, $focus_column) = $redirstreeview->get_cursor;
	return 0 if !$path;
	my $model = $redirstreeview->get_model;
	my $iter=$model->get_iter( $path );
	my ( $proto, $hport, $gip, $gport) = $model->get($iter,(0..3));
	return "$proto:$hport:$gip:$gport";
}

# Get the redir rule from the entry widgets.
# args: none
sub pull_redir_rule {

	my $guestip = $gladexml->get_widget('guestoctet1entry')->get_text .".".
		$gladexml->get_widget('guestoctet2entry')->get_text .".".
		$gladexml->get_widget('guestoctet3entry')->get_text .".".
		$gladexml->get_widget('guestoctet4entry')->get_text;
	my $proto = $gladexml->get_widget('tcpprotoradiobutton')->get_active;
	if($proto){ $proto = 'tcp'; }else{ $proto = 'udp'; }

	my $hostport =  $gladexml->get_widget('hostportspinbutton')->get_value;
	my $guestport = $gladexml->get_widget('guestportspinbutton')->get_value;
	return "$proto:$hostport:$guestip:$guestport";
}

# Set the values from $redir_data in the entry widgets
# args: $redir_data
sub push_redir_rule {
	my $redir_data = shift;
	my ( $proto, $hport, $gip, $gport) = split ':', $redir_data;
	my @octets = split '\.', $gip;
	$gladexml->get_widget('guestoctet1entry')->set_text($octets[0]);
	$gladexml->get_widget('guestoctet2entry')->set_text($octets[1]);
	$gladexml->get_widget('guestoctet3entry')->set_text($octets[2]);
	$gladexml->get_widget('guestoctet4entry')->set_text($octets[3]);
	if($proto eq 'tcp' ){
		my $proto = $gladexml->get_widget('tcpprotoradiobutton')->set_active(1);
	}else{
		my $proto = $gladexml->get_widget('udpprotoradiobutton')->set_active(1);		
	}
	$gladexml->get_widget('hostportspinbutton')->set_value($hport);
	$gladexml->get_widget('guestportspinbutton')->set_value($gport);
}


######################################################################
# 
# Startup
#
######################################################################


setlocale(LC_MESSAGES, "");
#bindtextdomain("qemu-launcher",  "./,locale");
bind_textdomain_codeset( "qemu-launcher" ,'');
textdomain('qemu-launcher');

#if an argument was given, assume we are launching from the
#command line.
if( scalar @ARGV ){
	$quick_launch = TRUE;;
}else{
	Gnome2::Program->init ($application_name, $version);
	Gtk2->init;
	$gladexml = Gtk2::GladeXML->new( $glade_files_dir.'/qemulauncher.glade');
}

#create default main config if none exists
if( ! -e "$conf_dir/$conf_file" ){
	mkdir $conf_dir if( ! -e $conf_dir );	
	save_main_config( \%main_config_defaults );
}
if( ! get_main_config() ){
	alert_user( gettext("There was a problem reading the main configuration. Starting with defaults.") );
	$main_config = \%main_config_defaults;
}

set_main_config() if ! $quick_launch;

@vm_list = list_vm_configs();

# launch the qemu session specified on the command line
if( $quick_launch ){
	if( in_vm_list( $ARGV[0] )){
		run_vm( load_vm_config( $ARGV[0] ) );
		exit 0;
	}else{
		alert_user(gettext(sprintf("A configuration named '%s' does not exist.", $ARGV[0])));
		exit 1;
	}
}

#populate the vm config list
my $idx = 0;
my $defaults_idx = -1;
foreach my $name ( @vm_list ){
	add_vm_to_list( $name );	
	$defaults_idx = $idx if $name eq $defaults_name;
	$idx++;
}
($gladexml->get_widget('vmnamecomboboxentry'))->set_active($defaults_idx);

print "main(): Config list:\n".Dumper(\@vm_list) if DEBUG_LEVEL > 1;

#create default config if none exists
if( ! -f "$conf_dir/$defaults_name" ){
	print "main(): Creating default config\n" if DEBUG_LEVEL > 1;
	save_vm_config_to_file( $defaults_name, \%default_config );
}
$default_config{'comment'} = gettext($default_config{'comment'});

#
# Setup signal handlers
#

my $mainwindow = $gladexml->get_widget('mainwindow'); 
$mainwindow->signal_connect ('delete_event' => sub { Gtk2->main_quit; TRUE });

my $cfgapplybutton = $gladexml->get_widget('cfgapplybutton');
$cfgapplybutton->signal_connect_swapped ('clicked', \&apply_main_config);

my $cfgsavebutton = $gladexml->get_widget('cfgsavebutton');
$cfgsavebutton->signal_connect_swapped ('clicked', \&cb_save_config );

my $cfgcancelbutton = $gladexml->get_widget('cfgcancelbutton');
$cfgcancelbutton->signal_connect_swapped ('clicked', \&set_main_config );

my $vmnamecomboboxentry = $gladexml->get_widget('vmnamecomboboxentry');
$vmnamecomboboxentry->signal_connect ('changed', \&show_vm_config);

my $savevmbutton = $gladexml->get_widget('savevmbutton');
$savevmbutton->signal_connect ('clicked', \&save_vm_config);

my $deletevmbutton = $gladexml->get_widget('deletevmbutton');
$deletevmbutton->signal_connect ('clicked', \&delete_vm_config);

my $execvmbutton = $gladexml->get_widget('execvmbutton');
$execvmbutton->signal_connect_swapped ('clicked', \&run_vm);

my $opentunradiobutton = $gladexml->get_widget('opentunradiobutton');
$opentunradiobutton->signal_connect ('toggled'=>sub{
	($gladexml->get_widget('tunfdspinbutton'))->set_sensitive($opentunradiobutton ->get_active());	
} );

my $tuntapradiobutton = $gladexml->get_widget('tuntapradiobutton');
$tuntapradiobutton->signal_connect ('toggled'=>sub{
	($gladexml->get_widget('tunscriptfileentry'))->set_sensitive($tuntapradiobutton ->get_active());	
} );

my $usermoderadiobutton = $gladexml->get_widget('usermoderadiobutton');
$usermoderadiobutton->signal_connect ('toggled'=>sub{
	my $state = $usermoderadiobutton ->get_active();
	($gladexml->get_widget('smbdirfileentry'))->set_sensitive($state);	
	($gladexml->get_widget('redirstreeview'))->set_sensitive($state);
	($gladexml->get_widget('rediraddbutton'))->set_sensitive($state);
	($gladexml->get_widget('redirapplybutton'))->set_sensitive($state);
	($gladexml->get_widget('redirremovebutton'))->set_sensitive($state);
	($gladexml->get_widget('tcpprotoradiobutton'))->set_sensitive($state);
	($gladexml->get_widget('udpprotoradiobutton'))->set_sensitive($state);
	($gladexml->get_widget('hostportspinbutton'))->set_sensitive($state);
	($gladexml->get_widget('guestportspinbutton'))->set_sensitive($state);
	($gladexml->get_widget('guestoctet1entry'))->set_sensitive($state);
	($gladexml->get_widget('guestoctet2entry'))->set_sensitive($state);
	($gladexml->get_widget('guestoctet3entry'))->set_sensitive($state);
	($gladexml->get_widget('guestoctet4entry'))->set_sensitive($state);
} );

my $loadstatecheckbutton = $gladexml->get_widget('loadstatecheckbutton');
$loadstatecheckbutton->signal_connect ('toggled'=>sub{
	($gladexml->get_widget('statefileentry'))->set_sensitive($loadstatecheckbutton->get_active());	
} );

# deal with port redir widgets...
my $rediraddbutton = $gladexml->get_widget('rediraddbutton');
$rediraddbutton->signal_connect ('clicked'=>sub{
	my @redirs = pull_redirs;
	my $redir = pull_redir_rule;
	@redirs = add_redir( $redir, \@redirs );
	push_redirs( \@redirs );
} );
my $redirremovebutton = $gladexml->get_widget('redirremovebutton');
$redirremovebutton->signal_connect ('clicked'=>sub{
	my @redirs = pull_redirs;
	my $redir = get_selected_redir;
	if( $redir ){
		@redirs = del_redir( $redir, \@redirs );
		push_redirs( \@redirs );
	}
} );
my $redirapplybutton = $gladexml->get_widget('redirapplybutton');
$redirapplybutton->signal_connect ('clicked'=>sub{
	my $redir = pull_redir_rule;
	my $old_redir = get_selected_redir;
	if( $old_redir ){
		my @redirs = change_redir( $redir, $old_redir);
		push_redirs( \@redirs );
	}
} );
my $redirstreeview = $gladexml->get_widget('redirstreeview');
$redirstreeview->signal_connect ('cursor-changed'=>sub{
	my $treeview = shift;
	my ($path, $focus_column) = $treeview->get_cursor;
	my $model = $treeview->get_model;
	my $iter=$model->get_iter( $path );
	my ( $proto, $hport, $gip, $gport) = $model->get($iter,(0..3));
	push_redir_rule( "$proto:$hport:$gip:$gport" );
} );
#


my $nogfxcheckbutton = $gladexml->get_widget('nogfxcheckbutton');
$nogfxcheckbutton->signal_connect ('toggled'=>sub{
	my $state =!($nogfxcheckbutton->get_active());
	($gladexml->get_widget('gfxpciradiobutton'))->set_sensitive($state);	
	($gladexml->get_widget('gfxvgaradiobutton'))->set_sensitive($state);	
} );

my $logcheckbutton = $gladexml->get_widget('logcheckbutton');
$logcheckbutton->signal_connect ('toggled'=>sub{
	foreach my $logtype ( qw/cpu exec in_asm int ioport op_opt op out_asm pcall/ ){
		($gladexml->get_widget($logtype.'togglebutton'))->set_sensitive($logcheckbutton ->get_active());	
	}
} );

my $linuxbootcheckbutton = $gladexml->get_widget('linuxbootcheckbutton');
$linuxbootcheckbutton->signal_connect ('toggled'=>sub{
	my $state = $linuxbootcheckbutton->get_active();
	($gladexml->get_widget('kernelcmdentry'))->set_sensitive($state);	
	($gladexml->get_widget('initrdfileentry'))->set_sensitive($state);	
	($gladexml->get_widget('kernelfileentry'))->set_sensitive($state);	
	($gladexml->get_widget('bootcombobox'))->set_sensitive(! $state);
} );

my $usecdromcheckbutton = $gladexml->get_widget('usecdromcheckbutton');
$usecdromcheckbutton->signal_connect ('toggled'=>sub{
	my $state = $usecdromcheckbutton->get_active();
	($gladexml->get_widget('cdfileentry'))->set_sensitive($state);	
	($gladexml->get_widget('hdcfileentry'))->set_sensitive(! $state);
} );


foreach my $diskname ( qw/hda hdb hdc hdd/){
	my $button = $gladexml->get_widget($diskname."creimgbutton");
	$button->signal_connect ('clicked'=>sub{
		create_disk_image_dialog($diskname);
	} );
}

foreach my $diskname ( qw/fda fdb hda hdb hdc hdd cd/){
	($gladexml->get_widget($diskname.'fileentry'))->set_default_path($main_config->{'imagedir'});
}


#
# set some initial states
#
$nogfxcheckbutton->toggled();
$tuntapradiobutton->toggled();
$opentunradiobutton->toggled();	
$logcheckbutton->toggled();
$linuxbootcheckbutton->toggled();
$usecdromcheckbutton->toggled();
$loadstatecheckbutton->toggled();


#
# create some more widgets
#
my $renderer = Gtk2::CellRendererText->new;
my $column1 =  Gtk2::TreeViewColumn->new_with_attributes ("Proto", $renderer, "text", 0 );
my $column2 =  Gtk2::TreeViewColumn->new_with_attributes ("Host port", $renderer, "text", 1 );
my $column3 =  Gtk2::TreeViewColumn->new_with_attributes ("Guest IP", $renderer, "text", 2 );
my $column4 =  Gtk2::TreeViewColumn->new_with_attributes ("Guest port", $renderer, "text", 3 );
my $liststore = Gtk2::ListStore->new ( 'Glib::String', 'Glib::String', 'Glib::String', 'Glib::String');
$redirstreeview->set_model($liststore);
$redirstreeview->append_column ($column1);
$redirstreeview->append_column ($column2);
$redirstreeview->append_column ($column3);
$redirstreeview->append_column ($column4);
	
($gladexml->get_widget('abouttext'))->set_markup("\n<span size=\"xx-large\">"
	."<b>Qemu Launcher $version</b></span>\n\n"
	."(c) 2005 Erik Meitner, emeitner\@f2o.org\n\n"
	."QEMU is a trademark of Fabrice Bellard");

#ready
push_vm_config( load_vm_config($defaults_name) );
#go
Gtk2->main;
exit;
