#!/usr/bin/perl
# $Id: xglotv 355 2010-08-16 07:16:08Z bd $

use utf8;
use encoding utf8;

# Copyright 2004-2008 Bjorn Danielsson
# http://glotv.dax.nu/
#
# This file is part of GLOTV. GLOTV is free software; you can redistribute
# it and/or modify it under the terms of the GNU General Public License as
# published at this URL: http://www.gnu.org/licenses/gpl.html.

use vars qw(%config);
use Tk;
use Tk::FileSelect;
use Getopt::Long;
use Fcntl;
use Encode;
use Unicode::Normalize;
use strict;

my $PATHPREFIX = "/usr/local/glotv";
do "$PATHPREFIX/etc/glotv.conf" or die "Failed reading configuration file";

my $conf_index = -1;
my $movieroot = $config{movieroot} || "/home/video";
my $spooldir = $config{spooldir} || "/home/video/recordings";
my $freqtable = $config{frequencies};
my $glotvrecord_pidfile_path = $config{rec_pidfile} || "/tmp/glotvrecord.pid";

my $opt_host = $config{glotv_server};
my $opt_glotv_server = undef;
my $opt_tvdisplay = undef;
my $opt_input = undef;
my $opt_fullhd = undef;
my $opt_default_file = undef;

GetOptions('host=s' => \$opt_host,
	   'glotv-server=s' => \$opt_glotv_server,
	   'tvdisplay=s' => \$opt_tvdisplay,
	   'input=s' => \$opt_input,
	   'fullhd' => \$opt_fullhd,
	   'default-file=s' => \$opt_default_file,	# for easier debugging
	   );

if ($opt_glotv_server) {
    print STDERR 'Option "--glotv-server" is deprecated, use "--host" instead.';
    print STDERR "\n";
    $opt_host = $opt_glotv_server;
}

my $kiosk_mode = 0;
my $kiosk_tk_resources = "kiosk-720x576-resources";
my $glotvd_host = undef;
my $glotvd_port = undef;
my @glotv_cmd = ("$PATHPREFIX/glotv");

my $widescreen = 0;
my $underscan = 0;
my $deinterlace = 1;
my $deinterlace_filteropt = "yadif=1";
my $mplayer_prog = "mplayer";
my %glotvd_status = ();

if (defined($opt_host)) {
    ($glotvd_host,$glotvd_port) = split(/:/, $opt_host);
    push @glotv_cmd, "--host=$glotvd_host";
    push @glotv_cmd, "--port=$glotvd_port" if $glotvd_port;
}

if (defined($config{kiosk_tk_resources})) {
    $kiosk_tk_resources = $config{kiosk_tk_resources};
} elsif ($opt_fullhd) {
    $kiosk_tk_resources = "kiosk-1920x1080-resources";
}

if (defined($opt_input)) {
    &kiosk_init_input();
}

my ($MW,$BF1,$BF2,$foo,$foolabel);
my ($kiosk_chandigits);
my $splash_window;

my @splash_texts =
    ("Gnu Lightweight Opuscule TeleVision",

     "Version 2.0 alpha10",

     "Copyright (c) 2004-2010 Björn Danielsson",

     "Latest version plus info about GLOTV can be found at " .
     "http://glotv.dax.nu" .
     "\n" .
     "Have fun, and don't forget to buy a larger disk! :-)",

     "GLOTV is free software. It is distributed under the terms of " .
     "the GNU General Public License. For more details, " .
     "see http://www.gnu.org/licenses/gpl.html",

     "GLOTV is pronounced \"gloo-TEH-veh\""
     );

my $mplayer_fifo = $config{mplayer_fifo} || "/tmp/mplayer-commands.fifo";
my $mplayer_active = 0;
my $mplayer_restart_allowed = 0;
my $recproc_active = 0;
my $paused = 0;
my $playing_speed = 1;
my $mplayer_termination_callback = undef;
my $mplayer_xy_option = undef;
my $mplayer_kiosk_opts = undef;

my $playbutlabel1 = "Play TV";
my $playbutlabel2 = "Stop Play";
my $playbutlabel = undef;

my $pausebutlabel1 = "Pause";
my $pausebutlabel2 = "Unpause";
my $pausebutlabel = undef;

my $mutebutlabel1 = "Mute";
my $mutebutlabel2 = "Unmute";
my $mutebutlabel = undef;

my $recordbutlabel1 = "Record";
my $recordbutlabel2 = "Stop Rec";
my $recordbutlabel = $recordbutlabel1;

my $tunerbutlabel = "Tuner 0";

sub reset_mplayerbuttons {
    $playbutlabel = $playbutlabel1;
    $pausebutlabel = $pausebutlabel1;
    $mutebutlabel = $mutebutlabel1;
}

my $seek_speed = 1.0;

sub change_the_channel {
    my $channel = shift;
    if ($channel >= 0) {
	system { $glotv_cmd[0] } @glotv_cmd, "--command=channel $channel";
    }
}

sub toggle_recording {
    if ($recproc_active) {
	system(join(" ", @glotv_cmd, "--abort-recording", "&"));
    } else {
	my @args = ();
	push @args, "--host=$glotvd_host" if defined($glotvd_host);
	push @args, "--host=$glotvd_port" if defined($glotvd_port);
	system(join(" ", @glotv_cmd, "--record", "&"));
    }
    $recproc_active = 1 - $recproc_active;
    &kiosk_activate_indicator() if $recproc_active;
}

sub latest_spoolfile {
    my $index = shift || 0;
    opendir SPOOL, $spooldir or die "Couldn't open spool directory: $!";
    my @spoolnames = readdir(SPOOL);
    closedir SPOOL;
    my @candidates = sort { $b cmp $a } grep m/^\d+-.+[a-z]\d\d\.mpg$/i, @spoolnames;
    if (@candidates) {

	return $spooldir . "/" . $candidates[$index];
    }
    return undef;
}

sub mplayer_start {
    my $mplayer = shift;
    $mplayer_restart_allowed = shift || 0;
    open MPLAYER, "$mplayer |"
	or die "Couldn't start mplayer process: $!";
    fcntl(MPLAYER, F_SETFL, O_NONBLOCK) or die "fcntl() failed: $!\n";
    $MW->fileevent(\*MPLAYER, readable => \&mplayer_event);
    $mplayer_active = 1;
    $seek_speed = 1.0;
    $playing_speed = 1;
    $playbutlabel = $playbutlabel2;
}

sub mplayer_event {
    my $line = <MPLAYER>;
    if (!defined($line)) {
	$MW->fileevent(\*MPLAYER, readable => "");
	close(MPLAYER);
	$mplayer_active = 0;
	print STDERR "MPlayer termination detected.\n";
	&reset_mplayerbuttons();
	if ($mplayer_termination_callback) {
	    my ($fun,@args) = @$mplayer_termination_callback;
	    &$fun(@args);
	}
    } else {
	print STDERR $line;
    }
}

sub mplayer_quit {
    $MW->fileevent(\*MPLAYER, readable => "");
    &mplayer_command("quit");
    $mplayer_active = 0;
    &reset_mplayerbuttons();
}

sub mplayer_command {
    my $cmd = shift;
    my $osd = shift;
    return unless $mplayer_active;
    eval {
	local $SIG{ALRM} = sub { die "alarm\n" };
	alarm(1);
	if (!$paused) {
	    if ($cmd eq "pause" && $playing_speed != 1) {
		$playing_speed = 1;
		&mplayer_write_fifo("speed_set 1");
	    }
	} elsif ($cmd ne "pause" && $cmd ne "pausing frame_step") {
	    &mplayer_write_fifo("pause");
	    $pausebutlabel = $pausebutlabel1;
	}
	&mplayer_write_fifo("osd_show_text $osd") if $osd;
	&mplayer_write_fifo($cmd);
	alarm(0);
    };
    if ($@) {
	die unless $@ eq "alarm\n";
	print STDERR "Hmm, mplayer seems to have died...\n";
	$mplayer_active = 0;
	$paused = 0;
	&reset_mplayerbuttons();
    }
}

sub mplayer_write_fifo {
    my $cmd = shift;
    return unless $mplayer_active;
    $paused = 1-$paused if $cmd eq "pause";		# Toggle state
    open FIFO, ">$mplayer_fifo";
    print FIFO "$cmd\n";
    print STDERR "$cmd\n";
    close FIFO;
}

sub quotify {
    my $arg = shift;
    $arg =~ s/'/'\\''/g;
    return "'$arg'";
}

sub mplayer_setup_common_opts {
    my $opts = "-quiet";
    $opts .= " -framedrop";
    $opts .= " -ass";
    $opts .= " -input file=$mplayer_fifo";
    $opts .= " -nograbpointer" if $kiosk_mode;
    $opts .= " $mplayer_kiosk_opts" if $mplayer_kiosk_opts;
    $opts .= " $config{mplayer_opts}" if $config{mplayer_opts};

    my $filters = "expand=osd=1";
    if ($deinterlace) {
	$filters = $deinterlace_filteropt . "," . $filters;
    }
    if ($mplayer_xy_option) {
	$opts .= " -xy ${mplayer_xy_option}";
	if ($widescreen) {
	    $opts .= " -monitorpixelaspect 117:160";
	} else {
	    $opts .= " -monitorpixelaspect 39:40";
	}
    } elsif ($mplayer_kiosk_opts) {
	if ($opt_fullhd) {
	    if ($widescreen) {
		$opts .= " -monitorpixelaspect 117:160";
	    } else {
		$opts .= " -monitorpixelaspect 39:40";
	    }
	} else {
	    if ($widescreen) {
		$opts .= " -monitorpixelaspect 16:11";
	    } else {
		$opts .= " -monitorpixelaspect 12:11";
	    }
	}
    }

    return ($opts,$filters);
}

sub mplayer_play_live {
    my $channel = shift;
    &change_the_channel($channel);
    if (!$mplayer_active) {
	my ($opts,$filters) = &mplayer_setup_common_opts();
	$opts .= " -cache 2048";
	$opts .= " -autosync 10";
	$opts .= " -vf $filters" if defined($filters);

	my $cmd = sprintf("%s %s -", $mplayer_prog, $opts);

	&mplayer_quit() if $mplayer_active;

	&mplayer_start(join(" ", @glotv_cmd, "--command=play", "--exec=".&quotify($cmd)), 1);
    }
}

sub mplayer_play_file {
    my $file = shift;
    if ($file) {
	if ($file =~ m/\.(ram|ra|rm)$/) {
	    if (open METAFILE, "<$file") {
		$file = <METAFILE>;
		chomp $file;
		close METAFILE;
	    }
	}
	my ($opts,$filters) = &mplayer_setup_common_opts();
	$opts .= " -cache 2048";
	$opts .= " -autosync 20";
	$opts .= " -vf $filters" if defined($filters);

	my $cmd = sprintf("%s %s %s", $mplayer_prog, $opts, &quotify($file));

	&mplayer_quit() if $mplayer_active;
	print STDERR "$cmd\n";
	&mplayer_start("$cmd < /dev/null", 0);
    }
}

sub mplayer_restart {
    &mplayer_quit();
    &mplayer_play_live(-1);				# Only restartable mode for now.
}


my $default_file = $opt_default_file;

sub default_file {
    if (defined($default_file)) {
	return $default_file 
    } else {
	return &latest_spoolfile();
    }
}

my $autorepeat_initial_speed = 2;
my $autorepeat_callback = undef;
my $autorepeat_counter = 0;
my $autorepeat_ignore = 0;
my $autorepeat_stoptime = 0;
my $autorepeat_muted = 0;

sub setup_autorepeat {
    $autorepeat_callback = shift;
    $autorepeat_ignore = shift || 0;
    $autorepeat_counter = 0;
    &autorepeat_handler();
}

sub autorepeat_handler {
    if ($autorepeat_callback) {
	$MW->after(500, \&autorepeat_handler);
	if ($autorepeat_counter >= $autorepeat_ignore) {
	    my ($fun,@args) = @$autorepeat_callback;
	    &$fun(@args);
	}
	$autorepeat_counter++;
    } else {
	$autorepeat_stoptime = time;
	if ($autorepeat_muted) {
	    &mplayer_command("mute");
	    $autorepeat_muted = 0;
	}
    }
}

sub repeating_seek {
    my $fmt = shift;
    my $value = shift;
    if ($autorepeat_counter == $autorepeat_ignore) {
	my $t = time - $autorepeat_stoptime;
	if ($t >= 3) {
	    $seek_speed = 1.0;
	} elsif ($t > 1 && $seek_speed >= 2.0) {
	    $seek_speed = $seek_speed/2;
	}
    } else {
	$seek_speed = $seek_speed * 1.10;
    }
    if (!$autorepeat_muted) {
	&mplayer_command("mute");
	$autorepeat_muted = 1;
    }
    &mplayer_command(sprintf $fmt, int($seek_speed * $value));
}

sub init_freqtable {
    if (!defined($freqtable)) {
	if (open PIPE, join(" ", @glotv_cmd, "--command=list_channels", "|")) {
	    $freqtable = {};
	    foreach my $line (<PIPE>) {
		chomp $line;
		my ($channo,$chandef) = split(/ /, $line, 2);
		$$freqtable{$channo} = $chandef;
	    }
	    close PIPE;
	} else {
	    $freqtable = { 'VHF5' => '175.25 Default channel' };
	}
    }
}


# Desktop Control Functions

sub aboutbutcmd { &show_splash_window(); }

sub osdbutcmd { &mplayer_command("osd"); }

sub playbutcmd {
    if ($playbutlabel eq $playbutlabel1) {
	&mplayer_play_live(-1);
    } else {
	&mplayer_quit();
    }
}

sub recordbutcmd {
    &toggle_recording();
    if ($recordbutlabel eq $recordbutlabel1) {
	$recordbutlabel = $recordbutlabel2;
	$default_file = undef;
    } else {
	$recordbutlabel = $recordbutlabel1;
    }
}

sub tunerbutcmd {
    &next_alternative_tuner();
    $tunerbutlabel = "Tuner " . ($conf_index+1);
}

sub pausebutcmd {
    &mplayer_command("pause");
    if ($pausebutlabel eq $pausebutlabel1) {
	$paused = 1;
	$pausebutlabel = $pausebutlabel2;
    } else {
	$paused = 0;
	$pausebutlabel = $pausebutlabel1;
    }
}

sub mutebutcmd {
    &mplayer_command("mute");
    if ($mutebutlabel eq $mutebutlabel1) {
	$mutebutlabel = $mutebutlabel2;
    } else {
	$mutebutlabel = $mutebutlabel1;
    }
}

sub playfilebutcmd {
    my $file = $MW->FileSelect(-directory => $movieroot, regexp => '(\.\.|[^.].*)', -width => 40)->Show;
    if (defined($file) && -r $file) {
	$default_file = $file;
	&mplayer_play_file($file);
    }
}

sub replaybutcmd {
    &mplayer_play_file(&default_file());
}

sub seekrevbutcmd {
    &setup_autorepeat([\&repeating_seek, "seek -%s", $autorepeat_initial_speed]);
}

sub seekfwdbutcmd {
    &setup_autorepeat([\&repeating_seek, "seek %s", $autorepeat_initial_speed]);
}

sub seekstopbutcmd {
    $autorepeat_callback = undef;
}

sub quitbutcmd {
    if ($mplayer_active) {
	&mplayer_quit();
	sleep(1);
    }
    exit(0);
}

my $chanmenu = undef;
my $chanmenuopt = undef;
my $chanmenu_inited = undef;

sub chanmenucmd {
    my $choice = shift;
    if (!$chanmenu_inited) {
	$chanmenu_inited = 1;
    } else {
	$glotvd_status{frequency} = "0";	# Force channel recalculation
	&mplayer_play_live($choice);
    }
}

sub chanmenulabel {
    my $value = shift;
    my $options = $chanmenu->cget('-options');
    foreach my $item (@$options) {
	return $$item[0] if $value == $$item[1];
    }
    return 0;
}

sub chanmenuitems {
    my @chanopts = ();
    foreach my $c (sort {$a <=> $b} keys %$freqtable) {
	my ($mhz,$data) = split(/\s+/, $$freqtable{$c}, 2);
	push @chanopts, ["${c}. - ${data}", $c];
    }
    return \@chanopts;
}

my $sizemenuopt = undef;

sub sizemenucmd {
    my $choice = shift;
    if ($choice =~ m/(\d+(\.\d+)?)\s*x/) {
	$mplayer_xy_option = $1;
    } else {
	$mplayer_xy_option = undef;
    }
    if ($mplayer_restart_allowed) {
	if ($mplayer_active) {
	    &mplayer_restart();
	}
    }
}

sub desktop_clock {
    &update_glotvd_status();
    &update_glotvrecord_status();
    $MW->after(10000, \&desktop_clock);
}

sub show_splash_window {
    if (!$splash_window || !Exists($splash_window)) {
	$splash_window = $MW->Toplevel();
	$splash_window->bind('<KeyRelease>' => sub {$splash_window->UnmapWindow();});
	my $width = "768";
	my $height = "576";
	my $splash = $splash_window->Photo(-file => "$PATHPREFIX/etc/glotv-${width}x${height}.gif");

	my $splashwidget = $splash_window->Canvas(-takefocus => 0,
						  -width => $width,
						  -height => $height,
						  -tile => $splash);
	$splashwidget->packPropagate(0);
	$splashwidget->pack();
	my $splashframe = $splashwidget->Frame(-width => 0.78*$width,
					       -height => 0.50*$height,
					       -background => 'gray95');
	$splashframe->place(-in => $splashwidget,
		     -relx => 0.5,
		     -y => 0.38*$height,
		     -anchor => 'n');
	$splashframe->packPropagate(0);

	my ($text1,@textn) = @splash_texts;
	my $msg1 = $splashframe->Message(-justify => 'center', -width => 0.63*$width,
					 -background => 'gray95', -foreground => 'black',
					 -font => ['Helvetica', 14, 'bold'],
					 -text => $text1);
	$msg1->pack(-pady => 0.03*$height);

	foreach my $text (@textn) {
	    my $msg = $splashframe->Message(-justify => 'center',
					    -width => 0.63*$width,
					    -background => 'gray95', -foreground => 'black',
					    -font => ['Helvetica', 10, 'bold'],
					    -text => $text);
	    $msg->pack(-pady => 0.01*$height);
	}

    } else {
	$splash_window->MapWindow();
    }
}

sub setup_desktop_display {
    $MW->bind('<Control-c>' => \&exit);
    $MW->bind('<q>' => \&exit);
    $MW->bind('<t>' => \&kiosk_terminal);

    $BF1 = $MW->Frame();

    &reset_mplayerbuttons();

    # Create a Play File button.
    my $playfilebut = $BF1->Button(-text => "Play File", -command => \&playfilebutcmd);
    $playfilebut->pack();

    # Create a pause-button.
    my $pausebut = $BF1->Button(-textvariable => \$pausebutlabel, -command => \&pausebutcmd);
    $pausebut->pack();

    # Create a play-button.
    my $playbut = $BF1->Button(-textvariable => \$playbutlabel, -command => \&playbutcmd);
    $playbut->pack();

    # Create a record-button.
    my $recordbut = $BF1->Button(-textvariable => \$recordbutlabel, -command => \&recordbutcmd);
    $recordbut->pack();

    # Create a replay-button.
    my $replaybut = $BF1->Button(-text => "Replay", -command => \&replaybutcmd);
    $replaybut->pack();

    # Create a reverse-button.
    my $seekrevbut = $BF1->Button(-text => "Reverse", -command => \&seekstopbutcmd);
    $seekrevbut->bind('<ButtonPress>' => \&seekrevbutcmd); 
    $seekrevbut->pack();

    # Create a forward-button.
    my $seekfwdbut = $BF1->Button(-text => "Forward", -command => \&seekstopbutcmd);
    $seekfwdbut->bind('<ButtonPress>' => \&seekfwdbutcmd);
    $seekfwdbut->pack();

    # Create a OSD-button.
    my $osdbut = $BF1->Button(-text => "OSD", -command => \&osdbutcmd);
    $osdbut->pack();

    # Create a Mute-button.
    my $mutebut = $BF1->Button(-textvariable => \$mutebutlabel, -command => \&mutebutcmd);
    $mutebut->pack();

    # Create a Quit-button.
    my $quitbut = $BF1->Button(-text => "Quit", -command => \&quitbutcmd);
    $quitbut->pack();

    # Create an About-button.
    my $aboutbut = $BF1->Button(-text => "About", -command => \&aboutbutcmd);
    $aboutbut->pack();

    # Create a tuner-button.
    my $tunerbut = $BF1->Button(-textvariable => \$tunerbutlabel, -command => \&tunerbutcmd);
    $tunerbut->pack();

    # Create the channel menu.
    my $dummy_placeholder = undef;					# Needed for Perl/Tk bug.
    $chanmenu = $MW->Optionmenu(-options => &chanmenuitems(),
				-command => \&chanmenucmd,
				-variable => \$dummy_placeholder,
				-textvariable => \$chanmenuopt);
    $chanmenu->pack();

    # Create the size menu frame.
    my $F2 = $MW->Frame();

    # Create the size menu.
    my @sizeopts = ('default resolution', '1.0 x', '0.83 x', '0.71 x', '0.50 x', '0.33 x');
    my $sizemenu = $F2->Optionmenu(-options => [@sizeopts],
				    -command => \&sizemenucmd,
				    -variable => \$sizemenuopt);

    # Create the Widescreen (anamorphic) checkbox.
    my $widescreencheckbox = $F2->Checkbutton(-variable => \$widescreen);

    $F2->Label(-text => 'AWS')->grid($widescreencheckbox, $sizemenu, -sticky => 'nsew');
    $F2->pack();

    # Layout:

    $seekrevbut->grid($pausebut, $seekfwdbut, -sticky => 'nsew');
    $playbut->grid($replaybut, $playfilebut, -sticky => 'nsew');
    $mutebut->grid($osdbut, $aboutbut, -sticky => 'nsew');
    $quitbut->grid($recordbut, $tunerbut, -sticky => 'nsew');

    $MW->after(1000, \&desktop_clock);

    $BF1->pack();

    if ($opt_tvdisplay) {
	$mplayer_kiosk_opts = "-display $opt_tvdisplay -vo xv -fs";
    }
}

# Kiosk Control Functions

my $KWF1;
my $KWF2;

# The kiosk parameters defined here are defaults only.
# Look for the actual values in the Tk resources file.

my $kiosk_width = 768;
my $kiosk_height = 576;
my $kiosk_x = 0;
my $kiosk_y = 0;
my $kiosk_background = 'gray';
my $kiosk_focuscolor = 'white';
my $kiosk_browser;

my %inputprocstate_handler = ( 'main'		=> \&inputproccmd_main,
			       'mplayer'	=> \&inputproccmd_mplayer,
			       'filechooser'	=> \&inputproccmd_filechooser,
			       'fileviewer'	=> \&inputproccmd_fileviewer,
			       'modalbox'	=> \&inputproccmd_modalbox,
			       'aboutbox'	=> \&inputproccmd_aboutbox,
			      );
my $inputprocstate = 'main';
my $mplayer_contstate;

my $main_1or2chandigits = 2;
my $chandigitstext;
my $chandigits_freq;			# Rename this variable!

my $modalbox;
my $modalboxtext;
my $modalbox_contstate;
my $modalbox_callback;

my $aboutbox;
my $imagebox;
my $imagecanvas;
my $imagefit;
my $imageindex;
my $imageid = undef;
my $imagephoto = undef;
my $prepimagepath = "/tmp/glotvcurrentimage.pnm";

my $filechooser;
my $filechooser_dir;
my $filechooser_prevstate;
my $filechooser_pagesize;
my $filechooser_viewimage = 0;

my $kiosk_button_bg;
my $kiosk_button_fg;

sub kiosk_modalbox {
    $modalboxtext = shift;
    $modalbox_callback = shift;

    $modalboxtext .= "\n\nPress OK to confirm, BACK to cancel." if $modalbox_callback;

    if (!$modalbox) {
	$modalbox = $MW->Toplevel(-width => $kiosk_width,
				  -height => $kiosk_height,
				  -background => $kiosk_focuscolor,
				 );
	$modalbox->geometry("+0+0");
	$modalbox->overrideredirect(1);

	$modalbox->packPropagate(0);

	my $mbf1 = $modalbox->Frame(-width => 0.82*$kiosk_width,
				    -height => 0.82*$kiosk_height,
				    -background => $kiosk_focuscolor);
	$mbf1->place(-in => $modalbox,
		     -relx => 0.5,
		     -rely => 0.5,
		     -x => $kiosk_x,
		     -y => $kiosk_y,
		     -anchor => 'center');
	$mbf1->packPropagate(0);


	my $busymessage = $mbf1->Message(-justify => 'center',
					     -width => 0.63*$kiosk_width,
					     -textvariable => \$modalboxtext);
	$busymessage->pack(-expand => 1,
			   -ipadx => 0.05*$kiosk_width,
			   -ipady => 0.07*$kiosk_height);
    } else {
	$modalbox->MapWindow();
	$modalbox->raise();
    }
    $modalbox_contstate = $inputprocstate;
    $inputprocstate = 'modalbox' if $modalbox_callback;
}

sub kiosk_modalbox_exit {
    $modalbox_callback = undef;
    if ($modalbox->ismapped) {
	$modalbox->UnmapWindow();
	$inputprocstate = $modalbox_contstate;
    }
}

sub kiosk_init_input {
    if ($opt_input eq 'lirc') {
	open INPUT, "$PATHPREFIX/input-lirc -geometry +32000+32000 |"
	    or die "Couldn't start lirc listening process: $!";
    } elsif ($opt_input eq 'xboxir') {
	open INPUT, "$PATHPREFIX/input-xboxir -geometry +32000+32000 |"
	    or die "Couldn't start xboxir listening process: $!";
    } else {
	die "Unknown input process: ${opt_input}";
    }
    fcntl(INPUT, F_SETFL, O_NONBLOCK) or die "fcntl() failed: $!\n";
    $kiosk_mode = 1;
}

sub kiosk_stop_input {
    $MW->fileevent(\*INPUT, readable => "");
    close INPUT;
}

sub kiosk_activate_input {
    $MW->fileevent(\*INPUT, readable => \&inputproccmd);
}

sub kiosk_terminal {
    my $bg = $kiosk_background;
    my $fg = "#f8f8f8";
    my $geometry = "60x27+60+30";
    &kiosk_stop_input() if $kiosk_mode;
    system { "/usr/X11R6/bin/xterm" } "xterm",
	"-bg", $bg,
	"-fg", $fg,
	"-cm",
	"-fn", "-*-courier-medium-r-normal-*-18-*-*-*-*-*-iso8859-1",
	"-geometry", $geometry;
    if ($kiosk_mode) {
	&kiosk_init_input();
	&kiosk_activate_input();
    }
}

sub kiosk_aboutbox {
    if (!$aboutbox) {
	$aboutbox = $MW->Toplevel(-background => $kiosk_background);
	$aboutbox->geometry("+0+0");
	$aboutbox->overrideredirect(1);

	my $impath = $MW->optionGet('splashImage', '.');
	$impath = "$PATHPREFIX/etc/$impath" unless $impath =~ m/^\//;

	my $splash = $aboutbox->Photo(-file => $impath);
	my $splashwidget = $aboutbox->Canvas(-takefocus => 0,
					 -width => $kiosk_width,
					 -height => $kiosk_height,
					 -tile => $splash);
	$splashwidget->packPropagate(0);
	$splashwidget->pack();
	my $F1 = $splashwidget->Frame(-width => 0.78*$kiosk_width,
				      -height => 0.50*$kiosk_height);
	$kiosk_button_bg = $F1->cget('-background');
	$kiosk_button_fg = $F1->cget('-foreground');
	$F1->place(-in => $splashwidget,
		   -relx => 0.5,
		   -x => $kiosk_x,
		   -y => $kiosk_y + 0.38*$kiosk_height,
		   -anchor => 'n');
	$F1->packPropagate(0);

	my $F2 = $F1->Frame(-width => 0.63*$kiosk_width,
				-height => 0.38*$kiosk_height);

	$F2->place(-relx => 0.5,
		   -x => $kiosk_x,
		   -y => $kiosk_y + 0.04*$kiosk_height,
		   -anchor => 'n');
	$F2->packPropagate(0);

	my $splashframe = $F1;
	my ($text1,@textn) = @splash_texts;
	my $msg1 = $splashframe->Message(-justify => 'center', -width => 0.75*$kiosk_width,
					 -class => 'Splashtitle',
					 -text => $text1);
	$msg1->pack(-pady => 3);

	foreach my $text (@textn) {
	    my $msg = $splashframe->Message(-justify => 'center',
					    -width => 0.75*$kiosk_width,
					    -text => $text);
	    $msg->pack(-pady => 2);
	}
    } else {
	$aboutbox->MapWindow();
	$aboutbox->raise();
    }
    $inputprocstate = 'aboutbox';
}

sub kiosk_change_focus {
    my $newfocus = shift;
    my $oldfocus = $kiosk_browser->{focus};
    unless ($newfocus == $oldfocus) {
	if ($newfocus >= 0) { 
	    my $w = $kiosk_browser->{items}->[$newfocus];
	    $w->configure(-background => $kiosk_focuscolor);
	}
	if ($oldfocus >= 0) { 
	    my $w = $kiosk_browser->{items}[$oldfocus];
	    $w->configure(-background => $kiosk_button_bg);
	}
	$kiosk_browser->{focus} = $newfocus;
    }
}

sub kiosk_browse_nextpage {
    my $newindex = $kiosk_browser->{pagestart} + $kiosk_browser->{pagesize};
    unless ($newindex >= @{$kiosk_browser->{data}}) {
	$kiosk_browser->{pagestart} = $newindex;
	&kiosk_browser_populate(0);
    }
}

sub kiosk_browse_prevpage {
    my $newindex = $kiosk_browser->{pagestart} - $kiosk_browser->{pagesize};
    $newindex = 0 if $newindex < 0;
    if ($newindex < $kiosk_browser->{pagestart}) {
	$kiosk_browser->{pagestart} = $newindex;
	&kiosk_browser_populate(0);
    }
}

sub kiosk_browse_down {
    my $newfocus = $kiosk_browser->{focus} + 1;
    unless ($kiosk_browser->{pagestart} + $newfocus >= @{$kiosk_browser->{data}}) {
	if ($newfocus < $kiosk_browser->{pagesize}) {
	    &kiosk_change_focus($newfocus);
	} else {
	    $kiosk_browser->{pagestart} += 1;
	    &kiosk_browser_populate(-1);
	}
    }
}

sub kiosk_browse_up {
    my $newfocus = $kiosk_browser->{focus} - 1;
    if ($newfocus >= 0) {
	&kiosk_change_focus($newfocus);
    } elsif ($kiosk_browser->{pagestart} > 0) {
	$kiosk_browser->{pagestart} -= 1;
	&kiosk_browser_populate(0);
    }
}

sub kiosk_browser_populate {
    my $initial_focus = shift || 0;
    my $popcount = 0;
    foreach my $i (0..$kiosk_browser->{pagesize}-1) {
	my $button = $kiosk_browser->{items}->[$i];
	my $eindex = $kiosk_browser->{pagestart} + $i;
	my $entry = [undef,""];
	if ($eindex < @{$kiosk_browser->{data}}) {
	    $entry = $kiosk_browser->{data}->[$eindex];
	}
	$button->configure(-text => Unicode::Normalize::NFC($$entry[1]));
	$popcount++;
	last if $popcount >= $kiosk_browser->{pagesize};
    }
    $kiosk_browser->{navhelp} = "Press BACK to leave";
    if ($kiosk_browser->{pagestart} > 0) {
	$kiosk_browser->{navhelp} .= ", < for previous page";
    }
    if ($kiosk_browser->{pagestart} + $kiosk_browser->{pagesize} < @{$kiosk_browser->{data}}) {
	$kiosk_browser->{navhelp} .= ", > for next page";
    }
    if ($popcount > 0) {
	if ($initial_focus == -1) {
	    &kiosk_change_focus($popcount-1);
	} else {
	    &kiosk_change_focus($initial_focus);
	}
    }
}

sub kiosk_filechooser_confirm {
    &kiosk_filebtncmd($kiosk_browser->{focus});
}

sub kiosk_filebtncmd {
    my $i = shift;
    my $entry = $kiosk_browser->{data}->[$kiosk_browser->{pagestart} + $i];
    my $path = $filechooser_dir . $$entry[0];
    $path =~ s/\*$//;
    print STDERR "path=$path\n";
    if ( -f $path ) {
	&kiosk_play_file($path);
    } else {
	$path =~ s/\@$/\//;
	&kiosk_filechooser($path);
    }
}

sub kiosk_filechooser_exit {
    $KWF1->UnmapWindow();
    $inputprocstate = $filechooser_prevstate;
}

sub kiosk_filechooser_delete {
    my $i = $kiosk_browser->{focus};
    my $entry = $kiosk_browser->{data}->[$kiosk_browser->{pagestart} + $i];
    my $path = $filechooser_dir . $$entry[0];

    print STDERR "Preparing to delete $path\n";
    &kiosk_modalbox("Do you really want to delete the file $path\n\n?",
		    [\&kiosk_filechooser_really_delete, $path]);
}

sub kiosk_filechooser_really_delete {
    my $path = shift;
    unlink $path;
    print STDERR "Deleted file $path\n";
    $inputprocstate = 'filechooser';
    &kiosk_filechooser($filechooser_dir);
}

sub kiosk_toggle_widescreen {
    $widescreen = 1-$widescreen;
    if ($opt_fullhd) {
	&kiosk_modalbox("Assuming " . ($widescreen ? "anamorphic" : "normal") . " video");
    } else {
	&kiosk_modalbox("Assuming screen aspect " . ($widescreen ? "16:9" : "4:3"));
    }
    $MW->after(2000, \&kiosk_modalbox_exit);
}

sub kiosk_toggle_underscan {
    $underscan = 1-$underscan;
    if ($underscan) {
	$mplayer_kiosk_opts = "-vo x11 -zoom -geometry 60%:48% -xy 670";
    } else {
	$mplayer_kiosk_opts = "-vo xv -fs";
    }
    &kiosk_modalbox("Switching to " . ($underscan ? "underscan" : "overscan") . " mode");
    $MW->after(2000, \&kiosk_modalbox_exit);
}

sub kiosk_toggle_deinterlace {
    $deinterlace = 1-$deinterlace;
    &kiosk_modalbox("De-interlacing is now turned " . ($deinterlace ? "on" : "off"));
    $MW->after(2000, \&kiosk_modalbox_exit);    
}

sub kiosk_filechooser_init {
    if (!$KWF1) {
	$KWF1 = $MW->Toplevel(-width => $kiosk_width,
			      -height => $kiosk_height,
			      -background => $kiosk_background);
	$KWF1->packPropagate(0);
	$KWF1->geometry("+0+0");
	$KWF1->overrideredirect(1);
    } else {
	$KWF1->MapWindow();
	$KWF1->raise();
    }
    if (!$KWF2) {
	$KWF2 = $KWF1->Frame(-width => 0.82*$kiosk_width,
			     -height => 0.82*$kiosk_height,
			     -background => $kiosk_background);
	$KWF2->place(-in => $KWF1,
		     -relx => 0.5,
		     -rely => 0.5,
		     -x => $kiosk_x,
		     -y => $kiosk_y,
		     -anchor => 'center');
	$KWF2->packPropagate(0);

	$KWF1->bind('<Right>' => \&kiosk_browse_nextpage);
	$KWF1->bind('<Left>' => \&kiosk_browse_prevpage);
	$KWF1->bind('<Up>' => \&kiosk_browse_up);
	$KWF1->bind('<Down>' => \&kiosk_browse_down);
	$KWF1->bind('<b>' => \&kiosk_filechooser_exit);
	$KWF1->bind('<Return>' => \&kiosk_filechooser_confirm);
	$KWF1->bind("<KeyPress-0>" => \&kiosk_filechooser_delete);

	# Create the filechooser object (a hash ref).
	$filechooser = { pagesize => $filechooser_pagesize };

	my @buttons = ();
	foreach my $i (0..$filechooser->{pagesize}-1) {
	    my $button = $KWF2->Button(-text => "", -width => 999, -anchor => 'w',
				       -highlightbackground => $kiosk_button_bg,
				       -command => [\&kiosk_filebtncmd, $i]);
	    push @buttons, $button;
	    $button->pack(-anchor => 'nw');
	}
	$filechooser->{items} = \@buttons;
	$filechooser->{navhelp} = 'Press BACK to leave, < for previous page, > for next';
	my $navhelplabel = $KWF2->Label(-textvariable => \$filechooser->{navhelp},
				     -background => $kiosk_background);
	$navhelplabel->pack(-anchor => 'sw', -expand => 1);

	$filechooser->{cmdhelp} = 'Press OK to play a file, 0 to delete it';
	my $cmdhelplabel = $KWF2->Label(-textvariable => \$filechooser->{cmdhelp},
					-background => $kiosk_background);
	$cmdhelplabel->pack(-anchor => 'sw');

	$filechooser->{focus} = -1;
    }
}

sub spoolsortkey {
    my $name = shift;
    if ($name =~ m/^(\d+-.+[a-z])(\d\d)\.mpg$/i) {
	return $1 . sprintf("%04d", 10000-$2);
    } elsif ($name =~ m/^\.\.\/?$/) {
	return "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";	# Cute trick
    } else {
	return lc($name);
    }
}

sub kiosk_filechooser {
    my $dir = shift || "$movieroot/";

    &kiosk_filechooser_init();
    if ($inputprocstate ne 'filechooser') {
	$filechooser_prevstate = $inputprocstate;
	$inputprocstate = 'filechooser';
    }

    my @movienames = ();
    $dir =~ s/[^\/]+\/\.\.\/?$//;
    my @listing = `ls -FasU --si '$dir'`;
    shift @listing;						# Remove "total" summary line
    foreach my $line (@listing) {
	chop $line;
	my ($size,$name) = $line =~ m/^\s*(\d\S*) (.*)$/;
	next if $name =~ m/^\.(_|DS_Store$)/;			# Filter MacOS noise
	next if $name =~ m/^\.\/?$/;
	if ($name =~ m/^\.\.\/?$/) {
	    next if $dir =~ m/^$movieroot\/?$/;
	}
	$line = "     $name" if $name =~ m/[\/\@]$/;
	$name =~ s/\*$//;
	push @movienames, [$name,$line];
    }
    my @entries = undef;
    if ($dir eq "$spooldir/") {
	@entries = sort {&spoolsortkey($$b[0]) cmp &spoolsortkey($$a[0])} @movienames;
    } else {
	@entries = sort { lc($$a[0]) cmp lc($$b[0]) } @movienames;
    }

    my $initial_focus = 0;
    if ($filechooser_dir && $filechooser_dir =~ m/^$dir./) {
	my $i = @entries;
	foreach (reverse @entries) {
	    $i--;
	    last if $dir . $_[0] eq $filechooser_dir;
	}
	$initial_focus = $i % $filechooser->{pagesize};
	$filechooser->{pagestart} = $i - $initial_focus;
    } else {
	$filechooser->{pagestart} = 0;
    }
    $filechooser_dir = $dir;
    $filechooser->{data} =\@entries;
    $kiosk_browser = $filechooser;
    &kiosk_browser_populate($initial_focus);
}

my $rotate_flag = 0;

sub kiosk_load_resized_image {
    my $path = shift;
    if ($path =~ m/\.(jpg|jpeg|png|gif|tiff|pnm)$/i) {
	my $width = $kiosk_width * $imagefit;
	my $height = $kiosk_height * $imagefit;
	my $size = $width . 'x' . $height;

	my @rotate_opt = ();
	if ($rotate_flag > 0) {
	    @rotate_opt = ("-rotate", 90*$rotate_flag);
	}

	my @convert_cmd = ("convert", "-depth", "8", "-size", $size, @rotate_opt, "-resize", $size);
	system { $convert_cmd[0] } @convert_cmd, $path, $prepimagepath;
	return $imagebox->Photo(-file => $prepimagepath);
    } else {
	return undef;
    }
}

sub kiosk_prefetch_image {
    my $fwd = shift;
    my $i = $kiosk_browser->{pagestart} + $kiosk_browser->{focus};
    $imagephoto = undef;
    while (1) {
	my $j = $i;
	if ($fwd) {
	    $j = $j+1 if $j < @{$kiosk_browser->{data}}-1;
	} else {
	    $j = $j-1 if $j > 0;
	}
	return if $i == $j;
	$i = $j;
	my $entry = $kiosk_browser->{data}->[$j];
	my $path = $filechooser_dir . $$entry[0];
	$path =~ s/\*$//;
	$imagephoto = &kiosk_load_resized_image($path);
	if ($imagephoto) {
	    $imageindex = $j;
	    last;
	}
    }
}

sub kiosk_fileviewer_showimage {
    if (!$imagebox) {
	$imagebox = $MW->Toplevel();
	$imagebox->geometry("-1-1");

	$imagecanvas = $imagebox->Canvas(-takefocus => 0,
					 -width => $kiosk_width,
					 -height => $kiosk_height,
					 -background => 'black',
					 -borderwidth => 0
					 );
	$imagecanvas->packPropagate(0);
	$imagecanvas->pack();
    } else {
	$imagebox->MapWindow();
	$imagebox->raise();
    }
    $imagecanvas->delete($imageid) if $imageid;
    $imageid = undef;
    my $i = $kiosk_browser->{pagestart} + $kiosk_browser->{focus};
    my $photo = $imagephoto;
    if (!$photo || $i != $imageindex) {
	my $entry = $kiosk_browser->{data}->[$i];
	my $path = $filechooser_dir . $$entry[0];
	$path =~ s/\*$//;
	$photo = &kiosk_load_resized_image($path);
    }
    if ($photo) {
	$imageid = $imagecanvas->createImage($kiosk_x + $kiosk_width/2,
					     $kiosk_y + $kiosk_height/2,
					     -image => $photo,
					     -anchor => 'center');
    } else {
	$imagebox->UnmapWindow();
    }
}

sub chandigitkeycmd {
    my $keysym = $Tk::event->K;
    &chandigitcmd($keysym);
}

sub chandigitsreset {
    my $chan = $glotvd_status{channel};
    if (defined $chan) {
	$chandigitstext = sprintf("%0*d", $main_1or2chandigits, $chan);
	(my $ignore, $chandigits_freq) = split(/\s+/, $$freqtable{$chan}, 2);
    } else {
	$chandigitstext = "-" x $main_1or2chandigits;
	$chandigits_freq = "";
    }
}

sub chandigitcmd {
    my $digit = shift;
    if ($chandigitstext =~ m/^\d+$/) {
	$chandigitstext = $digit . "-" x (length($chandigitstext)-1);
    } else {
	$chandigitstext =~ s/-/$digit/;
    }
    if ($chandigitstext =~ m/^\d+$/) {
	my $chan = $chandigitstext + 0;
	if ($$freqtable{$chan}) {
	    my ($freq,$chaninfo) = split(/\s+/, $$freqtable{$chan}, 2);
	    $chandigits_freq = "$chaninfo";
	    if ($inputprocstate eq 'main') {
		&kiosk_play_live($chan, $chaninfo);
	    } else {
		&change_the_channel($chan);
	    }
	}
	$MW->after(500, \&chandigitsreset);
    }
}

my $indicator_active = 0;
my $indicator_lambda = undef;
my $indicator_state = 'hidden';

sub kiosk_activate_indicator {
    if (!$indicator_active) {			# This works since Perl/Tk is single-threaded
	$indicator_active = 1;
	&kiosk_animate_indicator();
    }
}

sub kiosk_animate_indicator {
    if ($recproc_active) {
	my $interval = 700;
	if ($indicator_state eq 'hidden') {
	    $indicator_state = 'normal';
	} else {
	    $interval = 300;
	    $indicator_state = 'hidden';
	}
	&$indicator_lambda();
	$MW->after($interval, \&kiosk_animate_indicator);
    } else {
	if ($indicator_state ne 'hidden') {
	    $indicator_state = 'hidden';
	    &$indicator_lambda();
	}
	$indicator_active = 0;
    }
}

my $kiosk_status_message = "---";

sub update_glotvd_status {
    if (open PIPE, join(" ", @glotv_cmd, "--command=status", "|")) {
	my %status = ();
	foreach my $line (<PIPE>) {
	    chomp $line;
	    my ($key,$value) = split(/ /, $line, 2);
	    $status{$key} = $value;
	}
	close PIPE;
	my $bitrate = $status{video_bitrate};
	if ($bitrate >= 6000000) {
	    $status{recording_quality} = 'SP';
	} elsif ($bitrate > 0) {
	    $status{recording_quality} = 'LP';
	} else {
	    $status{recording_quality} = 'n/a';
	}
	my $newchan = undef;
	my $input = $status{input};
	my $freq = $status{frequency};
	if ($input ne $glotvd_status{input} || $freq ne $glotvd_status{frequency}) {
	    while (my ($chan,$data) = each %$freqtable) {
		if ($input eq 'television') {
		    $newchan = $chan if abs($freq - $data) < 0.3;
		} elsif ($input eq 's-video') {
		    $newchan = $chan if $data =~ m/^s-video|^svideo/i;
		} elsif ($input eq 'composite') {
		    $newchan = $chan if $data =~ m/^rca|^composite/i;
		} elsif ($input eq 'dvb') {
		    $newchan = $chan if $data =~ m/^dvb $freq$/i;
		}
	    }
	    print STDERR "Recalculated glotvd channel to: $newchan\n";
	    $status{channel} = $newchan;
	    $chandigitstext = sprintf("%0*d", $main_1or2chandigits, $newchan);
	    $chanmenuopt = &chanmenulabel($newchan) if $chanmenu;
	    (my $ignore, $chandigits_freq) = split(/\s+/, $$freqtable{$newchan}, 2);
	} else {
	    $status{channel} = $glotvd_status{channel};
	}
	%glotvd_status = %status;
	$kiosk_status_message = "Channel: $glotvd_status{channel} -- $chandigits_freq\n" .
	    "Deinterlace: " . ($deinterlace ? "on" : "off") . "\n" .
	    "Recording quality: $glotvd_status{recording_quality}\n" .
	    ($recproc_active? "Recording in progress\n" : "");
    }
}

sub update_glotvrecord_status {
    if ( -f $glotvrecord_pidfile_path ) {
	$recproc_active = 1;
	&kiosk_activate_indicator() if defined $indicator_lambda;
	$recordbutlabel = $recordbutlabel2;
    } else {
	$recproc_active = 0;
	$recordbutlabel = $recordbutlabel1;
    }
}

sub kiosk_play_live {
    my $chan = shift;
    my $descr = shift || "channel $chan";
    $mplayer_contstate = $inputprocstate;
    $inputprocstate = 'mplayer';
    $MW->after(500, \&chandigitsreset);
    if ($chan == -1) {
	$MW->after(500, sub {&kiosk_modalbox("Playing whatever is on, please wait...")});
	$MW->after(10000, \&kiosk_modalbox_exit);
    } else {
	$MW->after(500, sub {&kiosk_modalbox("Playing $descr, please wait...")});
	$MW->after(10000, \&kiosk_modalbox_exit);
    }
    $mplayer_termination_callback = [\&kiosk_modalbox_exit];
    &mplayer_play_live($chan);
}

sub kiosk_play_file {
    my $path = shift;
    $mplayer_contstate = $inputprocstate;
    $inputprocstate = 'mplayer';
    &kiosk_modalbox("Playing $path, please wait...");
    $modalbox_contstate = 'mplayer';
    $MW->after(10000, \&kiosk_modalbox_exit);
    $mplayer_termination_callback = [\&kiosk_modalbox_exit];
    &mplayer_play_file($path);
}

sub inputproccmd {
    my $line = <INPUT>;
    if (!defined($line)) {
	close(INPUT);
    } else {
	chomp($line);
	my ($keystring,$direction) = split(/ /, $line);
	&inputprocdispatch($keystring, $direction);
    }
}

sub inputprocdispatch {
    my $keystring = shift;
    my $direction = shift;
    print STDERR "# input command($inputprocstate): $keystring $direction\n";
    my $handler = $inputprocstate_handler{$inputprocstate};
    if ($handler) {
	&$handler($keystring, $direction);
    } else {
	die "Undefined state for input command: $inputprocstate";
    }
}

sub inputproccmd_modalbox {
    my $keystring = shift;
    my $direction = shift;
    if ($direction == 1 && $modalbox_callback) {
	if ($keystring eq 'Confirm') {
	    my ($fun,@args) = @$modalbox_callback;
	    &$fun(@args);
	    $modalbox_contstate = $inputprocstate;
	    &kiosk_modalbox_exit();
	} elsif ($keystring eq 'Back' || $keystring eq '0') {
	    &kiosk_modalbox_exit();
	}	
    }
}

sub inputproccmd_aboutbox {
    my $keystring = shift;
    my $direction = shift;
    if ($direction == 1) {
	$aboutbox->UnmapWindow();
	$inputprocstate = 'main';
    }
}

sub inputproccmd_fileviewer {
    my $keystring = shift;
    my $direction = shift;
    if ($direction == 1) {
	if ($keystring eq 'Prev') {
	    &kiosk_browse_up();
	    &kiosk_fileviewer_showimage();
	    $MW->after(10, [\&kiosk_prefetch_image, 0]);
	} elsif ($keystring eq 'Next') {
	    &kiosk_browse_down();
	    &kiosk_fileviewer_showimage();
	    $MW->after(10, [\&kiosk_prefetch_image, 1]);
	} elsif ($keystring eq 'Display') {
	    $inputprocstate = 'filechooser';
	    $imagebox->UnmapWindow();
	} elsif ($keystring eq 'Right') {
	    $rotate_flag++;
	    $rotate_flag = 0 if $rotate_flag > 3;
	    $imagephoto = undef;
	    &kiosk_fileviewer_showimage();
	} elsif ($keystring eq 'Left') {
	    $rotate_flag--;
	    $rotate_flag = 3 if $rotate_flag < 0;
	    $imagephoto = undef;
	    &kiosk_fileviewer_showimage();
	} elsif ($keystring eq 'Play') {
	    &kiosk_filechooser_confirm();
	} elsif ($keystring eq 'Back') {
	    $imagebox->UnmapWindow();
	    &kiosk_filechooser_exit();
	}
    }
}

sub inputproccmd_filechooser {
    my $keystring = shift;
    my $direction = shift;
    if ($direction == 1) {
	if ($keystring eq 'Prev') {
	    &setup_autorepeat([\&kiosk_browse_up], 6);
	    &kiosk_browse_up();
	} elsif ($keystring eq 'Next') {
	    &setup_autorepeat([\&kiosk_browse_down], 6);
	    &kiosk_browse_down();
	} elsif ($keystring eq 'Right') {
	    &kiosk_browse_nextpage();
	} elsif ($keystring eq 'Left') {
	    &kiosk_browse_prevpage();
	} elsif ($keystring eq 'Confirm') {
	    &kiosk_filechooser_confirm();
	} elsif ($keystring eq 'Play') {
	    &kiosk_filechooser_confirm();
	} elsif ($keystring eq 'Display') {
	    $inputprocstate = 'fileviewer';
	    $imageindex = undef;
	    $imagephoto = undef;
	    &kiosk_fileviewer_showimage();
	    $MW->after(10, [\&kiosk_prefetch_image, 1]);
	} elsif ($keystring eq 'Back') {
	    &kiosk_filechooser_exit();
	} elsif ($keystring eq 'Info') {
	    &kiosk_toggle_status_display();
	} elsif ($keystring eq 'Record') {
	    &toggle_recording();
	} elsif ($keystring eq 'Plus') {
	    &kiosk_toggle_widescreen();
	} elsif ($keystring eq 'Minus') {
	    if ($opt_fullhd) {
		&kiosk_toggle_deinterlace();
	    } else {
		&kiosk_toggle_underscan();
	    }
	} elsif ($keystring eq '0') {
	    &kiosk_filechooser_delete();
	} elsif ($keystring eq '1') {
	    &kiosk_play_file("dvd://1");
	}
    } elsif ($direction == 0) {
	if ($keystring eq 'Prev' || $keystring eq 'Next') {
	    $autorepeat_callback = undef;
	}
    }
}

sub inputproccmd_mplayer {
    my $keystring = shift;
    my $direction = shift;
    if (!$mplayer_active) {
	&kiosk_modalbox_exit();
	$inputprocstate = $mplayer_contstate;
	return &inputprocdispatch($keystring, $direction);
    }
    if ($direction == 1) {
	if ($keystring eq 'Play') {
	    if (!defined($autorepeat_callback)) {
		if ($paused) {
		    &mplayer_command("pausing frame_step");
		} else {
		    $playing_speed *= 2;
		    &mplayer_command(sprintf "speed_set %s", $playing_speed);
		}
	    }
	    $autorepeat_callback = undef;
	} else {
	    $autorepeat_callback = undef;
	    if ($keystring =~ m/^[0-9]$/) {
		&chandigitcmd($keystring);
	    } elsif ($keystring eq 'Stop') {
		&mplayer_quit();
	    } elsif ($keystring eq 'Pause') {
		&mplayer_command("pause");
	    } elsif ($keystring eq 'Seekrev') {
		&setup_autorepeat([\&repeating_seek, "seek -%s", $autorepeat_initial_speed]);
	    } elsif ($keystring eq 'Seekfwd') {
		&setup_autorepeat([\&repeating_seek, "seek %s", $autorepeat_initial_speed]);
	    } elsif ($keystring eq 'Display') {
		&mplayer_command("osd");
	    } elsif ($keystring eq 'Confirm') {
		&toggle_recording();
	    } elsif ($keystring eq 'Record') {
		&toggle_recording();
	    } elsif ($keystring eq 'Info') {
		&kiosk_toggle_status_display();
	    }
	}
    } elsif ($direction == 0) {
#	if ($keystring eq 'Seekrev' || $keystring eq 'Seekfwd') {
#	    $autorepeat_callback = undef;
#	}
    }
}

sub inputproccmd_main {
    my $keystring = shift;
    my $direction = shift;
 print STDERR "Got this input: $keystring: $direction\n";
    if ($direction == 1) {
	if ($keystring =~ m/^[0-9]$/) {
	    &chandigitcmd($keystring);
	} elsif ($keystring eq 'Play') {
	    if ($chandigitstext =~ m/^(\d)\D/) {
		$MW->after(500, \&chandigitsreset);
		&kiosk_play_live($1);
	    } else {
		&kiosk_play_live(-1);
	    }
	} elsif ($keystring eq 'Title') {
	    &kiosk_filechooser();
	} elsif ($keystring eq 'Confirm') {
	    &toggle_recording();
	} elsif ($keystring eq 'Record') {
	    &toggle_recording();
	} elsif ($keystring eq 'Back') {
	    if ($chandigitstext =~ m/^(\d)\D/) {
		$MW->after(500, \&chandigitsreset);
		&kiosk_play_file(&latest_spoolfile($1));
	    }
	} elsif ($keystring eq 'Info') {
	    &kiosk_aboutbox();
	} elsif ($keystring eq 'Plus') {
	    &kiosk_toggle_widescreen();
	} elsif ($keystring eq 'Minus') {
	    if ($opt_fullhd) {
		&kiosk_toggle_deinterlace();
	    } else {
		&kiosk_toggle_underscan();
	    }
	} elsif ($keystring eq 'Pause') {
	    &kiosk_terminal();
	} elsif ($keystring eq 'Tuner') {
	    &next_alternative_tuner();
	}
    }
}

sub next_alternative_tuner() {
    my $altconf = $config{alternatives} || [];
    if (@$altconf) {
	$conf_index++;
	my $conf = $$altconf[$conf_index];
	if (!defined($conf)) {
	    $conf_index = -1;
	    $conf = "glotv.conf";
	}
	%config = ();
	do "$PATHPREFIX/etc/$conf" or die "Failed reading $PATHPREFIX/etc/$conf";

	print STDERR "Switched to alternative configuration $conf\n";

	my $tuner = $conf_index + 1;
	@glotv_cmd = ("$PATHPREFIX/glotv", "--tuner=$tuner");

	$movieroot = $config{movieroot} || "/home/video";
	$spooldir = $config{spooldir} || "/home/video/recordings";
	$freqtable = $config{frequencies};
	$glotvrecord_pidfile_path = $config{rec_pidfile} || "/tmp/glotvrecord.pid";

	$opt_host = $config{glotv_server};
	if (defined($opt_host)) {
	    ($glotvd_host,$glotvd_port) = split(/:/, $opt_host);
	    push @glotv_cmd, "--host=$glotvd_host";
	    push @glotv_cmd, "--port=$glotvd_port" if $glotvd_port;
	}

	%glotvd_status = ();
	&init_freqtable();

	if ($kiosk_mode) {
	    my $alt_background = $config{alt_background};
	    if (!defined($alt_background)) {
		$alt_background = $MW->optionGet('kioskBackground', '.');
	    }
	    my $alt_foreground = $config{alt_foreground};
	    if (!defined($alt_foreground)) {
		$alt_foreground = $MW->optionGet('foreground', '.');
	    }
	    $kiosk_chandigits->configure(-background => $alt_background,
					 -foreground => $alt_foreground);

	    &update_kiosk_clock();
	} else {
	    $chanmenuopt = "";
	    $chanmenu_inited = undef;	# inhibit auto-play
	    $chanmenu->configure(-options => &chanmenuitems());
	    &update_glotvd_status();
	    &update_glotvrecord_status();
	}
    }
}

my $kiosk_clocktext = "####-##-## ##:##";
my $kiosk_prevminute = -1;
my $kiosk_freespace_text = "";

sub update_kiosk_clock {
    my ($second,$minute,$hour,$day,$month,$year) = localtime(time);
    if (1 || $minute != $kiosk_prevminute) {
	$kiosk_clocktext = sprintf("%04d-%02d-%02d %02d:%02d",
				   $year+1900, $month+1, $day,
				   $hour, $minute);
	my @df = `df --si $spooldir 2>/dev/null`;
	my $dfdata = pop @df;
	my ($dummy,$size,$used,$avail,$percent) = split(/\s+/, $dfdata);
	$kiosk_freespace_text = "Free: $avail ($percent full)";
	&update_glotvd_status();
	&update_glotvrecord_status();
    }
}

sub kiosk_clock {
    &update_kiosk_clock();
    $MW->after(5000, \&kiosk_clock);
}

my $kiosk_status_display_flag = 0;
my $kiosk_status_display;

sub kiosk_toggle_status_display {
    if ($kiosk_status_display_flag) {
	if ($kiosk_status_display) {
	    $kiosk_status_display->UnmapWindow();
	}
	$kiosk_status_display_flag = 0;
    } else {
	if (!$kiosk_status_display) {
	    $kiosk_status_display = $MW->Toplevel(-width => $kiosk_width,
						  -height => $kiosk_height,
						  -background => $kiosk_focuscolor,
				      );
	    $kiosk_status_display->geometry("+0+0");
	    $kiosk_status_display->overrideredirect(1);

	    $kiosk_status_display->packPropagate(0);

	    my $sd1 = $kiosk_status_display->Frame(-width => 0.82*$kiosk_width,
						   -height => 0.82*$kiosk_height,
						   -background => $kiosk_focuscolor);
	    $sd1->place(-in => $kiosk_status_display,
			 -relx => 0.5,
			 -rely => 0.5,
			 -x => $kiosk_x,
			 -y => $kiosk_y,
			 -anchor => 'center');
	    $sd1->packPropagate(0);

	    my $statusmsg = $sd1->Message(-justify => 'left',
					  -width => 0.63*$kiosk_width,
					  -textvariable => \$kiosk_status_message);
	    $statusmsg->pack(-expand => 1,
			     -ipadx => 0.05*$kiosk_width,
			     -ipady => 0.07*$kiosk_height);

	} else {
	    $kiosk_status_display->MapWindow();
	    $kiosk_status_display->raise();
	}
	$kiosk_status_display_flag = 1;
    }
}

sub kiosk_main_screen {
    my $path = $kiosk_tk_resources;
    $path = "$PATHPREFIX/etc/$path" unless $path =~ m/^\//;
    $MW->optionReadfile($path);
    $kiosk_width = $MW->optionGet('kioskWidth', '.');
    $kiosk_height = $MW->optionGet('kioskHeight', '.');
    $kiosk_x = $MW->optionGet('kioskX', '.');
    $kiosk_y = $MW->optionGet('kioskY', '.');
    $imagefit = $MW->optionGet('kioskImagefit', '.');
    $kiosk_background = $MW->optionGet('kioskBackground', '.');
    $kiosk_focuscolor = $MW->optionGet('kioskFocuscolor', '.');
    $filechooser_pagesize = $MW->optionGet('filechooserPagesize', '.');

    my $MWF0 = $MW->Toplevel(-background => $kiosk_background);
    $MWF0->geometry("+0+0");
    $MWF0->overrideredirect(1);

    foreach (0..9) {
	$MWF0->bind("<KeyPress-$_>" => \&chandigitkeycmd);
    }

    my $impath = $MW->optionGet('splashImage', '.');
    $impath = "$PATHPREFIX/etc/$impath" unless $impath =~ m/^\//;
    my $splash = $MWF0->Photo(-file => $impath);
    my $splashwidget = $MWF0->Canvas(-takefocus => 0,
				     -width => $kiosk_width,
				     -height => $kiosk_height,
				     -tile => $splash);

    $impath = $MW->optionGet('indicatorImage', '.');
    $impath = "$PATHPREFIX/etc/$impath" unless $impath =~ m/^\//;
    my $indicator = $MWF0->Photo(-file => $impath);
    my $indicator_item = $splashwidget->createImage(0, 0, -anchor => 'nw',
						    -image => $indicator,
						    -state => 'hidden');
    $indicator_lambda =
	sub {$splashwidget->itemconfigure($indicator_item, -state => $indicator_state)};

    $splashwidget->packPropagate(0);
    $splashwidget->pack();
    my $MWF1 = $splashwidget->Frame(-width => 0.78*$kiosk_width,
				    -height => 0.48*$kiosk_height);
    $kiosk_button_bg = $MWF1->cget('-background');
    $kiosk_button_fg = $MWF1->cget('-foreground');
    $MWF1->place(-in => $splashwidget,
		 -relx => 0.5,
		 -x => $kiosk_x,
		 -y => $kiosk_y + 0.38*$kiosk_height,
		 -anchor => 'n');
    $MWF1->packPropagate(0);

    my $MWF2 = $MWF1->Frame(-width => 0.63*$kiosk_width,
			    -height => 0.38*$kiosk_height);

    $MWF2->place(-relx => 0.5,
		 -x => $kiosk_x,
		 -y => $kiosk_y + 0.04*$kiosk_height,
		 -anchor => 'n');
    $MWF2->packPropagate(0);

    my $chanlabel = $MWF2->Label(-text => "Channel: ");
    $chanlabel->place(-relx => 0.0, -rely => 0.0, -anchor => 'nw');

    $chandigitstext = "-" x $main_1or2chandigits;
    $kiosk_chandigits = $MWF2->Label(-class => 'Chandigits', -textvariable => \$chandigitstext,
				     -width => 2,
				     -relief => 'flat',
				     -background => $kiosk_background,
				  );
    $kiosk_chandigits->place(-relx => 0.3, -rely => 0.0, -anchor => 'n');

    $chandigits_freq = "";
    my $mhzlabel = $MWF2->Label(-textvariable => \$chandigits_freq);
    $mhzlabel->place(-relx => 1.0, -rely => 0.0, -anchor => 'ne');

    my $clocklabel = $MWF2->Label(-textvariable => \$kiosk_clocktext);
    $clocklabel->place(-relx => 1.0, -rely => 0.35, -anchor => 'e');
    &kiosk_clock();

    my $freespacelabel = $MWF2->Label(-textvariable => \$kiosk_freespace_text, -justify => 'left');
    $freespacelabel->place(-relx => 0.0, -rely => 0.35, -anchor => 'w');

    my $verb = "start";
    my $mainhelp = "PLAY - play live video\n" .
	"OK - start/stop recording\n" .
	"GO - file browser\n" .
	"0-9 BACK - replay recording (0 for latest)\n" .
	"INFO - about this software\n" .
	"TV - switch tuner";
    my $helpmsg = $MWF2->Message(-justify => 'left',
				 -padx => 0,
				 -width => 0.63*$kiosk_width,
				 -textvariable => \$mainhelp);
    $helpmsg->place(-relx => 0.0, -rely => 1.0, -anchor => 'sw');

    $MW->after(100, sub {$MW->UnmapWindow()});		# Workaround for Fluxbox window manager

    $mplayer_kiosk_opts = "-vo xv -fs";
}

sub setup_kiosk_display {
#    $MW->geometry("+32000+32000");
    $MW->overrideredirect(1);
    $MW->geometry("+0+0");
    &kiosk_main_screen();
}

&init_freqtable();

# Make sure the mplayer command pipe exists.
if (! -p $mplayer_fifo) {
    unlink $mplayer_fifo;
    system("mkfifo $mplayer_fifo");
}

# Initialize Tk
$MW = MainWindow->new();
die "Couldn't create main window" unless defined($MW);

if ($opt_input) {
    &kiosk_activate_input();
}

if ($kiosk_mode) {
    &setup_kiosk_display();
} else {
    &setup_desktop_display();
}

&update_glotvd_status();

MainLoop();

