#!/usr/bin/perl -w

=head1 NAME

 ReMoot - the pseudo-universal remote control wrapper

=head1 VERSION

 The documentation below refers to the "daemoot" program in ReMoot 0.9

=head1 DESCRIPTION

 ReMoot is a command wrapper for a number of media programs running on
 GNU/Linux systems and possibly other *nix systems as well. The official
 version of the package is maintained by Andreas Wallberg
 (andreas.wallberg@gmail.com) and Joel Berglund (joel.berglund@gmail.com).

 "daemoot" is the daemon that waits for user specified arguments and passes
 them on to the relevant multimedia applications.

 "daemoot" is licenced under the Perl Artistic License 2.0. Please see
 http://www.perlfoundation.org/artistic_license_2_0 and
 http://www.fsf.org/licensing/licenses/index_html#PerlLicense for details.

 Copyright  2007 Andreas Wallberg <andreas.wallberg@gmail.com>

=cut

use strict;
use warnings;


# ============================================================================
# GLOBAL VARIABLES
# ============================================================================


# About this program
# ------------------

my %about = (

                "name"      => "daemoot" ,
                "version"   => "0.9 \"Vrgrd\"" ,
                "author"    => "Andreas Wallberg and Joel Berglund" ,
);


# Die if an other instance of daemoot is running
# ----------------------------------------------

my @running = `ps -u $ENV{USER} -o command`;

my $instance = 0;
foreach my $app ( @running ) {

    $instance++ if $app =~ m/(\/perl)+.+daemoot/;

}

die "ERROR! Not starting a second instance of daemoot!\n" if $instance > 1;


# External programs, pipes, ports, locations etc
# ----------------------------------------------

check_directory("$ENV{HOME}/.mplayer");
my $mplayer_fifo = "$ENV{HOME}/.mplayer/fifo";
check_fifo($mplayer_fifo);

my $vlc_port        = "8080";   # The default VLC port for web access

my $kmplayer = undef;           # Defined if kmplayer is running
my $kmplayer_pid = 1;           # This PID is updated if we detect kmplayer
my $mapped = undef;             # Defined if kmplayer has remapped mplayer

my $python = undef;             # Defined if we detect some python programs

my $smplayer = undef;           # Defined if smplayer is running
my $telnet;                     # Telnet interface loaded via IO::Socket::INET
my $smplayer_port = "8000";     # Default port for smplayer's telnet interface

# Application states
# ------------------

my $argument = undef;           # User input to be analysed and passed on
my $round = 1;                  # Incremented when a loop is completed
my $app_state = "running";      # Toggled between "running" and "stopped" when
                                # ALSA and DCOP is asked to print activities
$|=1;                           # Flush text immediately


# System variables
# ----------------

my $alsa = undef;
$alsa = "/proc/asound/timers" if -e "/proc/asound/timers";


# Known commands
# --------------

my %commands = (

                    "play"          => 1 ,
                    "pause"         => 1 ,
                    "playpause"     => 1 ,
                    "prev"          => 1 ,
                    "next"          => 1 ,
                    "stop"          => 1 ,
                    "volup"         => 1 ,
                    "voldown"       => 1 ,
                    "mute"          => 1 ,

);


# Application data
# ----------------

my %known_app;          # Holds the commands of each supported application
my %app_alias;          # A simple translation table between external and
                        # internal names of the applications.
my %running_app;        # List of all running and supported apps. Retrieved
                        # with "ps" and compared against %app_alias.
my %running_pid;        # List of PIDs of the apps, practical for cross-
                        # referencing when an app is multithreaded and
                        # any of the threads could be using alsa.
my %alsa_app;           # List of apps picked up by ALSA and DCOP.
my %last_running;       # List of apps that were running in the last cycle.
my %new;                # List of apps that have been started since the last
                        # cycle.
my %age;                # Keeps track on how many rounds/cycles/loops an
                        # app as been running.
my %popularity;         # Keeps track on how many times a particular app has
                        # been accessed.
my %dominating_app;     # Dominating apps are executed no matter which state
                        # (playing/paused/stopped) they have.
my %execute_app;        # List of all apps to be passed the user specified
                        # argument (we call this app execution)


# Populate the %known_app library
# -------------------------------

apps();


# Directories and files
# ---------------------

check_directory("$ENV{HOME}/.remoot");
my $fifo = "$ENV{HOME}/.remoot/fifo";
check_fifo($fifo);
my $helpfile = "$ENV{HOME}/.remoot/help";


# Argument input
# --------------

my %switches;
$switches{"-b"}->{"info"}   = "Benchmarking to estimate execution time.";
$switches{"-h"}->{"info"}   = "Print Help about usage.";
$switches{"-v"}->{"info"}   = "Use Verbose mode to help debugging.";
$switches{"-vlc"}->{"info"} =
"VLC port. Usage: \"-vlcNUMBER\"\n\t\t(no space). Default: 8080.";
$switches{"-smp"}->{"info"} =
"SMPlayer port. Usage: \"-smpNUMBER\"\n\t\t(no space). Default: 8000.";

# Benchmarking
# ------------

my $benchmark = 0;
my $start;
my $end;


# Verbose mode
# ------------

my $verbose = 0;


# Check switches passed on the command line
# -----------------------------------------

parse_argv() if @ARGV;


# Generate the helpfile if it does not exist
# ------------------------------------------

if ( not -e $helpfile ) {

        print_helpfile();

}


# ============================================================================
# MAIN PROGRAM LOOP
# ============================================================================


# The program is waiting for input on its fifo. Having received this it will
# process it and the start waiting again for the next input.

while (1) {

    # Open and read the named pipe
    # ----------------------------

    open (FIFO, "<" , "$fifo")
    or die "$fifo does not seem to be accessible:\n$!";

    $argument = (<FIFO>);

    $start = Benchmark->new if $benchmark;

    chomp $argument;

    # Verify and execute the argument
    # -------------------------------

    examine();

    # POSTFIX - reset states and prepare for next round etc.
    # -------

    close FIFO;
    verbose() if $verbose;

    postfix();

}


# ============================================================================
# SUBROUTINES
# ============================================================================


# parse_argv();
# -------------

=head2 parse_argv

 Check switches passed to daemoot

=cut

sub parse_argv {

    foreach my $arg ( @ARGV ) {

        if ( $arg eq "-b" ) {

            print "Benchmark mode\n";
            $benchmark = 1;
            use Benchmark;

        }

        elsif ( $arg eq "-h" ) {

            help(*STDOUT);
            print_helpfile();

        }

        elsif ( $arg eq "-v" ) {

            print "Verbose mode\n";
            $verbose = 1;

        }

        elsif ( $arg =~ m/-vlc(\d+)/i ) {

            $vlc_port = $1;
            print "VLC http port set to $vlc_port\n";

        }

        elsif ( $arg =~ m/-smp(\d+)/i ) {

            $smplayer_port = $1;
            print "SMPlayer telnet port set $smplayer_port\n";

        }

    }

}


# examine();
# ----------

=head2 examine

 This is where the passed argument is identified and eventually passed on to
 the supported and relevant multimedia apps. Audio volume is also adjusted.

=cut

sub examine {

    # We need to have a command passed to daemoot. Grumpy if not. This can
    # probably not happen anymore.

    unless ( $argument ) {

        help(*STDOUT);
        die "\nNo command passed to $about{name}!\n";

    }

    # -------------
    # Change volume
    # -------------

    # Here we look at the specified command and first check if it changes the
    # audio volume.

    # Increase volume

    if ( $argument eq "volup" ) {

        system "amixer sset Master 2+ 1> /dev/null &" if $alsa;
        end_benchmark() if $benchmark;

    }

    # Decrease volume

    elsif ( $argument eq "voldown" ) {

        system "amixer sset Master 2- 1> /dev/null &" if $alsa;
        end_benchmark() if $benchmark;

    }

    # Mute

    elsif ( $argument eq "mute" ) {

        system "amixer sset Master toggle 1> /dev/null &" if $alsa;
        end_benchmark() if $benchmark;

    }

    # --------------------
    # Control applications
    # --------------------

    # If we recognise the given argument and it was not about adjusting volume
    # we will try execute it for all relevant apps according to their status.

    elsif ( $commands{$argument} ) {

        # Get the a list of running and supported apps
        # --------------------------------------------

        running_apps();

        # New apps
        # --------

        # Check if any running app has been started since last time

        print "\n\n====================\n\n" if $verbose;
        print "Checking new apps...\n" if $verbose;

        new_apps();
        %last_running = %running_app;

        # Unless we found a new app...

        unless ( %execute_app ) {

            # Playing apps
            # ------------

            # Check which supported apps that are actively using alsa
            # (playing) at this moment. ALSA calls these applications
            # "running". Here we also check dominating apps.

            print "Checking playing and dominating apps...\n" if $verbose;
            playing_apps() if $alsa;

            # Unless we found any playing or dominating apps...

            unless ( %execute_app ) {

                # Paused apps
                # -----------

                # If no supported program was found to be playing, look for
                # paused programs that report to alsa as "stopped", to make
                # things a little extra confusing.

                print "Checking paused apps...\n" if $verbose;
                $app_state = "stopped";
                paused_apps() if $alsa;

                # Unless we found paused apps...

                unless ( %execute_app ) {

                    # Stopped apps
                    # ------------

                    # If there were no paused apps, pass the command to the
                    # most popular stopped app.

                    print "Checking stopped apps...\n" if $verbose;
                    stopped_apps();

                }

            }

        }

    }

    # Print help
    # ----------

    elsif ( $argument =~ m/^\-*help$/i or $argument =~ m/^\-*h$/i )  {

        help(*STDOUT);

    }

    elsif ( $argument eq "exit" ) {

        exit;

    }

    # Print help if the funny user passed a funny command to daemoot.

    else {

        help(*STDOUT);

    }

}


# running_apps();
# ---------------

=head2 running_apps

 Use the external shell command "ps" to get a list of the user's running
 processes and threads. Check if process names in this list belong to
 supported apps and remember those and their PIDs.

=cut

sub running_apps {

    # Retrieve ps info
    # ----------------

    my @apps = `ps -L -u $ENV{USER} -o comm,lwp`;

    shift @apps if @apps;

    # Scan @apps and filter out information about supported applications
    # ------------------------------------------------------------------

    foreach my $appline (@apps) {

        chomp $appline;

        my ( $name, $pid  ) = split /\s+/ , $appline;

        # Remember this application only if it is supported (has an alias)

        if ( $app_alias{ $name } ) {

            # We save the PID of the running application. If it is a threaded
            # application, we will see more child entries following this one,
            # but here we only save the parent.

            unless ( $running_app{ $app_alias{ $name } } ) {

                $running_app{ $app_alias{ $name } } = $pid unless
                $name =~ /python/;

                # Note that there might be python apps running

                if ( $app_alias{ $name } =~ /python/) {

                    $python = 1;

                }

            }

            # KMPLAYER
            # --------

            if ( $name eq "kmplayer" ) {

                $kmplayer = 1;

                # If this seems to be a new kmplayer session

                unless ( $pid == $kmplayer_pid ) {

                    print "Re-mapping kmplayer\n";

                    $kmplayer_pid = $pid;
                    remap_mplayer("kmplayer");

                }

            }

            # SMPLAYER
            # --------

            elsif ( $name eq "smplayer" ) {

                $smplayer = 1;

            }

            # Here we save the PID of both parent and child threads.

            $running_pid{ $pid } = $app_alias{ $name } unless
            $name =~ /python/ ;

        }

    }

    # Python exceptions
    # -----------------

    # For programs that call themselves "python" in the ps-list, run the list
    # again with new options.

    check_python_programs() if $python;

}


# new_apps();
# -----------

=head2 new_apps

 See if any supported application has been started since last time. Make this
 app dominating (and forget about any old dominating apps) and target it for
 downstream execution.

=cut

sub new_apps {

    # If the program was not running in the last round, it is new.

    my @new_apps;
    foreach my $app ( keys %running_app ) {

        if ( $running_app{$app} ) {

            unless ( $last_running{$app} ) {

                $new{$app} = 1;         # This is a new app
                $age{$app} = 1;         # It is one loop old
                push @new_apps, $app;

                $popularity{$app} = 0 unless $popularity{$app};

            }

        }

    }

    # Keep only the most popular app if there are more than one
    # ---------------------------------------------------------

    filter_apps(\@new_apps);

    # If there is a new app, pass the argument to it.

    if ( %execute_app ) {

        parse_alsa();

        # Execute the new app
        # -------------------

        execute(\%execute_app);
        %dominating_app = %execute_app;

        # If other apps were playing over alsa we pause these
        # ---------------------------------------------------

        my $true_argument = $argument;

        if ( %alsa_app ) {

            my %alsa_others;

            foreach my $app ( keys %alsa_app ) {

                $alsa_others{$app} = 1 unless $execute_app{$app};

            }

            if ( %alsa_others ) {

                $argument = "playpause";

                execute(\%alsa_others);

                $argument = $true_argument;

            }

        }

    }

    # Reset parameters for closed apps
    # --------------------------------

    foreach my $app ( keys %last_running ) {

        unless ( $running_app{$app} ) {

            $dominating_app{$app} = undef;
            $age{$app} = undef;

        }

    }

}

# filter_apps();
# --------------

=head2 filter_apps

 Select one app for execution.

=cut

sub filter_apps {

    my @list = @{ $_[0] };

    if ( @list > 1 ) {

        my $app = get_popular_app(@list);
        $execute_app{$app} = 1;

    }

    elsif ( @list == 1 ) {

        $execute_app{ $list[0] } = 1;

    }

}

# playing_apps();
# ---------------

=head2 playing_apps

 Check with ALSA and DCOP which apps are playing.

=cut

sub playing_apps {

    # Check ALSA
    # ----------

    parse_alsa();

    # Here we check the status of apps that may or may not use artsd as engine
    # , since the PID that /proc/asound/timers will report for these apps will
    # belong to artsd if they do.

    check_K_apps();

    # Grant dominating status to running but non-dominating apps
    # ----------------------------------------------------------

    my %running;
    foreach my $app ( keys %alsa_app ) {

        $running{$app} = 1 unless $dominating_app{$app};

    }

    if ( %running ) {

        # Discard old dominating apps

        %dominating_app = %running;

    }

    # Select dominating apps for execution
    # ------------------------------------

    my @dominating;
    foreach my $app ( keys %dominating_app ) {

        if ( $dominating_app{$app} ) {

            push @dominating, $app;

        }

    }

    # Keep only the most popular app if there are more than one
    # ---------------------------------------------------------

    filter_apps(\@dominating);

    # If we have not detected any dominating app we check the playing apps
    # --------------------------------------------------------------------

    unless ( %execute_app ) {

        my @playing = keys %alsa_app;

        # Keep only the most popular app if there are more than one
        # ---------------------------------------------------------

        filter_apps(\@playing);

    }

    # Pass the argument to the target apps
    # ------------------------------------

    if ( %execute_app ) {

        execute(\%execute_app);
        %dominating_app = %execute_app;

    }

    # Pause any playing apps that are not receiving an argument
    # ---------------------------------------------------------

    my %to_pause;
    foreach my $app ( keys %alsa_app ) {

        $to_pause{$app} = 1 unless $execute_app{$app};

    }

    if ( %to_pause ) {

        my $temp_argument = $argument;
        $argument = "playpause";

        execute(\%to_pause);
        $argument = $temp_argument;

    }

}


# paused_apps();
# --------------

=head2 paused_apps

 Check with ALSA and DCOP which apps are paused.

=cut

sub paused_apps {

    parse_alsa();

    # Here we check the status of apps that may or may not use artsd as engine
    # , since the PID that /proc/asound/timers will report for these apps will
    # belong to artsd if they do.

    check_K_apps();

    my @paused = keys %alsa_app;

    # Keep only the most popular app if there are more than one
    # ---------------------------------------------------------

    filter_apps(\@paused);

    if ( %execute_app ) {

        execute(\%execute_app);
        %dominating_app = %execute_app;

    }

}


# stopped_apps();
# ---------------

=head2 stopped_apps

 Collect and execute the stopped apps.

=cut

sub stopped_apps {

    my @stopped = keys %running_app;

    # Keep only the most popular app if there are more than one
    # ---------------------------------------------------------

    filter_apps(\@stopped);

    if ( %execute_app ) {

        execute(\%execute_app);
        %dominating_app = %execute_app;

    }

}


# parse_alsa();
# -------------

=head2 parse_alsa

 Use alsa's /proc/asound/timers to see which apps are using the sound card.
 First check for playing apps which are reported as "running" and then paused
 apps ("stopped") if no running ones were found.

=cut

sub parse_alsa {

    # Check ALSA
    # ----------

    my @active_list = `cat /proc/asound/timers | grep Client`;

    foreach my $app ( @active_list ) {

        if ( $app =~ m/^\D+(\d+)\s+\:\s+($app_state)\s*$/i ) {

            my $pid = $1;

            # Note the apps. Respect a few special cases
            # where the PID and its owner name may not be associated with the
            # the parent process

            # If we know this pid and app and it is not artsd, remember it

            if ( $running_pid{$pid} ) {

                unless ( $running_pid{$pid} eq "artsd" ) {

                    $alsa_app{ $running_pid{$pid} } = 1;

                }

            }

        }

    }

    if ( $app_state eq "running" ) {

        # KMPLAYER
        # --------

        # kmplayers "pause" can not start playing a stopped instance of the
        # program. We therefor assign "play" to the default "playpause" action
        # and adjust this is if it is found to be playing.

        if ( $kmplayer ) {

            $known_app{"mplayer"}->{"playpause"}
            = "dcop kmplayer-$kmplayer_pid KMediaPlayer pause"
            if $kmplayer and $alsa_app{"mplayer"};

        }

        # XMMS2
        # -----

        # xmms2 does not have any quick play/pause toggle so we need to swap
        # its commands around according to its status.

        if ( $alsa_app{"xmms2"} ) {

            $known_app{"xmms2"}->{"playpause"} = "xmms2 pause"

        }

    }
}


# get_popular_app();
# ------------------

=head2 get_popular_app

 If there is a list of candidate apps to execute, get the youngest most
 popular one.

=cut

sub get_popular_app {

    my $pop_score = 0;
    my @pop_apps;
    my $app_age;
    my @age_apps;

    # Get the most popular apps
    # -------------------------

    foreach my $app ( @_ ) {

        if ( $popularity{$app} > $pop_score ) {

            undef @pop_apps;
            push @pop_apps, $app;

            $pop_score = $popularity{$app};

        }

        elsif ( $popularity{$app} == $pop_score ) {

            push @pop_apps, $app;

        }

    }

    # If more than one most popular app, get the youngest
    # ---------------------------------------------------

    if ( @pop_apps > 1 ) {

        foreach my $app ( @pop_apps ) {

            $app_age = $age{$app} unless $app_age;

            if ( $age{$app} < $app_age ) {

                undef @age_apps;
                push @age_apps, $app;

                $app_age = $age{$app};

            }

            elsif ( $age{$app} == $app_age ) {

                push @age_apps, $app;

            }

        }

        # If more than one youngest most popular app, return one of them
        # --------------------------------------------------------------

        return $age_apps[0];

    }

    else {

        # Return the most popular app
        # ---------------------------

        return $pop_apps[0];

    }

}


# check_directory();
# ------------------

=head2 check_directory

 Verify that the directory exists and attempt to create it if not.

=cut

sub check_directory {

    my $dir = $_[0];

    unless ( -d $dir ) {

        mkdir($dir, 0755) or die "Unable to create new directory $dir:\n$!";

    }

}


# check_fifo();
# -------------

=head2 check_fifo

 Verify that the named pipe (fifo) exists and attempt to create it if not.

=cut

sub check_fifo {

    my $fifo = $_[0];

    unless ( -p "$fifo" ) {

        system ("mkfifo $fifo") == 0
        or die "Unable to create named pipe $fifo:\n$!";

    }

}


# check_K_apps();
# ---------------

=head2 check_K_apps

 KDE programs may or may not hide behind the artsd sound server. Use dcop to
 query their status.

=cut

sub check_K_apps {

    # If we are looking for running apps

    if ( $app_state eq "running" ) {

        if ( $running_app{"amarok"} ) {

            # ... check if Amarok is running.

            if ( `dcop amarok player status` =~ /2/ ) {

                $alsa_app{"amarok"} = 1;

            }

        }

        if ( $running_app{"juk"} ) {

            # ... check if juk is running.

            if ( `dcop juk Player status` =~ /2/ ) {

                $alsa_app{"juk"} = 1;

            }

        }

        if ( $running_app{"noatun"} ) {

            # ... check if noatun is running.

            if ( `dcop noatun Noatun state` =~ /2/ ) {

                $alsa_app{"noatun"} = 1;

            }

        }

        if ( $running_app{"kscd"} ) {

            # ... check if kscd is running.

            if ( `dcop kscd CDPlayer getStatus` =~ /2/ ) {

                $alsa_app{"kscd"} = 1;

            }

        }

    }

    # If we are looking for paused apps...

    else {

        if ( $running_app{"amarok"} ) {

            # ... check if Amarok is paused.

            if ( `dcop amarok player status` =~ /1/ ) {

                $alsa_app{"amarok"} = 1;

            }

        }

        if ( $running_app{"juk"} ) {

            # ... check if juk is paused.

            if ( `dcop juk Player status` =~ /1/ ) {

                $alsa_app{"juk"} = 1;

            }

        }

        if ( $running_app{"noatun"} ) {

            # ... check if noatun is paused.

            if ( `dcop noatun Noatun state` =~ /1/ ) {

                $alsa_app{"noatun"} = 1;

            }

        }

        if ( $running_app{"kscd"} ) {

            # ... check if kscd is paused.

            if ( `dcop kscd CDPlayer getStatus` =~ /4/ ) {

                $alsa_app{"kscd"} = 1;

            }

        }

    }

}


# check_python_programs();
# ------------------------

=head2 check_python_programs

 Some python program, like exaile, may just be identified as "python" in the
 first "ps" analysis. Run a second "ps" specifically for those apps.

=cut

sub check_python_programs {

    my @apps =
    `ps -L -u $ENV{USER} -o command,lwp`;

    # Scan @apps and filter out information about supported applications.

    my @python_apps = ( "exaile" , "pytone" );

    foreach my $appline (@apps) {

        foreach my $app ( @python_apps ) {

            if ( $appline =~ /python\s+$app[.py]*\s+(\d+)/ ) {

                my $pid = $1;

                unless ( $running_app{ $app_alias{$app} } ) {

                    $running_app{ $app_alias{$app} } = $pid;

                }

                # Here we save the PID

                $running_pid{ $pid } = $app_alias{ $app };

            }

        }

    }

}


# execute();
# ----------

=head2 execute

 If the passed command is known for the particular app, execute it.

=cut

sub execute {

    my $execute_ref = $_[0];

    foreach my $app ( keys %{ $execute_ref } ) {

        if ( $execute_ref->{$app} ) {

            # If the particular command is defined for that app, we execute
            # the command.

            if ( $known_app{ $app }->{ $argument } and $running_app{ $app } )

            {

                # Use smplayer's telnet interface

                if ( $app eq "mplayer" and $smplayer ) {

                    smplayer_telnet();

                }

                else {

                    print "\nExecuting $app: $known_app{$app}->{$argument}\n";
                    system $known_app{ $app }->{ $argument };
                    $popularity{$app}++;

                }

            }

            end_benchmark() if $benchmark;

        }

    }

}


# smplayer_telnet();
# ------------------

=head2 smplayer_telnet

 Used to control smplayer over a telnet session.

=cut

sub smplayer_telnet {

    print "\nExecuting smplayer: $known_app{smplayer}->{$argument}";

    # Open and switch to telnet output

    require IO::Socket;
    $telnet = IO::Socket::INET->new("localhost:$smplayer_port")
    or die "I can't connect to localhost: $!";
    $telnet->autoflush(1);

    select($telnet);
    $|=1;

    print $known_app{"smplayer"}->{ $argument };
    sleep 1;
    close $telnet;

    select(STDOUT);
    $|=1;

    $popularity{"mplayer"}++;

}


# end_benchmark();
# ----------------

=head2 end_benchmark

 End the benchmark :-)

=cut

sub end_benchmark {

    $end = Benchmark->new;
    my $diff = timediff( $end, $start );
    print "Execution time: " . timestr( $diff ) . "\n";

}


# verbose();
# ----------

=head2 verbose

 Print detailed status about all apps. Updated every round.

=cut

sub verbose {

    print "\n";
    print "Stats for round $round\n";

    print "\nNew:";
    foreach my $app ( sort keys %new ) {

    print "\t$app" if $new{$app};

    }

    print "\nRunning:";
    foreach my $app ( sort keys %running_app ) {

    print "\t$app" if $running_app{$app};

    }

    print "\nAlsa:\t";
    foreach my $app ( sort keys %alsa_app ) {

    print "\t$app" if $alsa_app{$app};

    }

    print "\nDominating:";
    foreach my $app ( sort keys %dominating_app ) {

    print "\t$app" if $dominating_app{$app};

    }

    print "\nAge:\n\n";
    foreach my $app ( sort keys %age ) {

    print "\t$app\t$age{$app}\n" if $age{$app};

    }

    print "\n\nPopularity:\n\n";
    foreach my $app ( sort keys %popularity ) {

    print "\t$app\t$popularity{$app}\n" if $popularity{$app};

    }

    print "\n";

}


# postfix();
# ----------

=head2 postfix

 Bump app ages and reset states until next round.

=cut

sub postfix {

    # Increase the age of all running apps
    # ------------------------------------

    foreach my $app ( keys %running_app ) {

        $age{$app}++;

    }

    $round++;

    # XMMS2
    # -----

    # Reset XMMS2 - assume it is paused or stopped until proven otherwise

    $known_app{"xmms2"}->{"playpause"} = "xmms2 play";

    # SMPLAYER
    # --------

    # Forget we have smplayer running

    $smplayer = undef if $smplayer;

    # KMPLAYER
    # --------

    # Reset mplayer if it has been remapped to kmplayer but it is no longer
    # running.

    if ( $mapped and not defined $kmplayer ) {

        remap_mplayer("mplayer");
        $mapped = undef;

    }

    $known_app{"mplayer"}->{"playpause"}
    = "dcop kmplayer-$kmplayer_pid KMediaPlayer play" if $kmplayer;
    $kmplayer = undef;

    # DAEMOOT
    # -------

    # Flush the old application data.

    undef %running_app;
    undef %running_pid;
    undef %alsa_app;
    undef %execute_app;
    undef %new;

    # Reset state

    $app_state = "running";

}

# print_helpfile;
# ---------------

=head2 print_helpfile

 Start a helpfile.

=cut

sub print_helpfile {

    unlink $helpfile if $helpfile;
    open(HELP, "> $helpfile")
    or die "Unable to start help file $helpfile:\n$!";

    help(*HELP);
    close HELP;

}

# help();
# -------

=head2 help

 Print help to a file or to STDOUT.

=cut

sub help {

    my $out = $_[0];

    print $out "\nThis is $about{name} by $about{author}\n";
    print $out "Version $about{version} - expect fun and breakage!\n\n";

    print $out "The program is a simple pseudo-universal command wrapper\n";
    print $out "to media programs that accepts remotely passed commands.\n\n";

    print $out "$about{name} is a daemon that is meant to run as a\n";
    print $out "background process. It is waiting for input passed as\n";
    print $out "arguments to the special file/pipe $fifo.\n\n";

    print $out "You can send arguments to that pipe using many means:\n\n";

    print $out "    echo playpause > $fifo\n\n";

    print $out "which is fast, or, if \"remoot\" is in your \$PATH:\n\n";

    print $out "    remoot playpause\n\n";

    print $out "which is a little less responsive, but perhaps easier to\n";
    print $out "work with and it also detects and starts daemoot for you.\n";
    print $out "Commands like this can be executed on the command line or\n";
    print $out "mapped to multimedia keys, buttons, icons, web interfaces\n";
    print $out "- you name it!\n\n";

    print $out "Command line switches\n";
    print $out "=====================\n\n";

    foreach my $switch (sort keys %switches) {

        print $out "\t$switch\t$switches{$switch}->{info}\n";

    }

    print $out "\nAvailable remote control arguments";
    print $out "\n==================================\n\n";

    foreach my $cmd (sort keys %commands) {

        print $out "\t$cmd\n";

    }

    print $out "\nSupported programs and arguments";
    print $out "\n================================\n\n";

    my $nr = 1;

    foreach my $app (sort keys %known_app) {

        print $out "\t$nr. $app -- ";
        $nr++;

        foreach my $cmd (sort keys %{ $known_app{$app} } ) {

            print $out "$cmd ";

        }

        print $out "\n";

    }

    print $out "\n";

    print $out "Visit http://sourceforge.net/projects/remoot/\n";
    print $out "for more info!\n\n";

}


# remap_mplayer();
# ----------------

=head2 remap_mplayer

 Point mplayer to kmplayer.

=cut

sub remap_mplayer {

    if ( $_[0] eq "kmplayer" ) {

        $known_app{"mplayer"}->{"play"}
        = "dcop kmplayer-$kmplayer_pid KMediaPlayer play";

        $known_app{"mplayer"}->{"pause"}
        = "dcop kmplayer-$kmplayer_pid KMediaPlayer pause";

        $known_app{"mplayer"}->{"playpause"}
        = "dcop kmplayer-$kmplayer_pid KMediaPlayer pause";

        $known_app{"mplayer"}->{"prev"} = undef;
        $known_app{"mplayer"}->{"next"} = undef;

        $known_app{"mplayer"}->{"stop"}
        = "dcop kmplayer-$kmplayer_pid KMediaPlayer stop";

        $mapped = 1;

    }

    else {

        $known_app{"mplayer"}->{"play"}      = "echo pause > $mplayer_fifo &";
        $known_app{"mplayer"}->{"pause"}     = "echo pause > $mplayer_fifo &";
        $known_app{"mplayer"}->{"playpause"} = "echo pause > $mplayer_fifo &";

        $known_app{"mplayer"}->{"prev"}
        = "echo \"seek -20\" > $mplayer_fifo &";

        $known_app{"mplayer"}->{"next"}
        = "echo \"seek +20\" > $mplayer_fifo &";

        $known_app{"mplayer"}->{"stop"}     = "echo quit > $mplayer_fifo &";

    }

}


# apps();
# -------

=head2 apps

 This is the application database which holds all commands that can be passed
 to all supported applications.

=cut

sub apps {


    # AMAROK
    # ------

    $known_app{"amarok"}->{"play"}          = "amarok -p &";
    $known_app{"amarok"}->{"pause"}         = "amarok --pause &";
    $known_app{"amarok"}->{"playpause"}     = "amarok -t &";
    $known_app{"amarok"}->{"prev"}          = "amarok -r &";
    $known_app{"amarok"}->{"next"}          = "amarok -f &";
    $known_app{"amarok"}->{"stop"}          = "amarok -s &";

    $app_alias{"amarokapp"} = "amarok";
    $app_alias{"amarok"} = "amarok";


    # AQUALUNG
    # --------

    $known_app{"aqualung"}->{"play"}         = "aqualung --play &";
    $known_app{"aqualung"}->{"pause"}        = "aqualung --pause &";
    $known_app{"aqualung"}->{"playpause"}    = "aqualung --pause &";
    $known_app{"aqualung"}->{"prev"}         = "aqualung --back &";
    $known_app{"aqualung"}->{"next"}         = "aqualung --fwd &";
    $known_app{"aqualung"}->{"stop"}         = "aqualung --stop &";

    $app_alias{"aqualung"} = "aqualung";


    # AUDACIOUS
    # ---------

    $known_app{"audacious"}->{"play"}         = "audacious --play &";
    $known_app{"audacious"}->{"pause"}        = "audacious --pause &";
    $known_app{"audacious"}->{"playpause"}    = "audacious --play-pause &";
    $known_app{"audacious"}->{"prev"}         = "audacious --rew &";
    $known_app{"audacious"}->{"next"}         = "audacious --fwd &";
    $known_app{"audacious"}->{"stop"}         = "audacious --stop &";

    $app_alias{"audacious"} = "audacious";


    # BANSHEE
    # -------

    $known_app{"banshee"}->{"play"}         = "banshee --play &";
    $known_app{"banshee"}->{"pause"}        = "banshee --pause &";
    $known_app{"banshee"}->{"playpause"}    = "banshee --toggle-playing &";
    $known_app{"banshee"}->{"prev"}         = "banshee --previous &";
    $known_app{"banshee"}->{"next"}         = "banshee --next &";
    $known_app{"banshee"}->{"stop"}         = "banshee --pause &";

    $app_alias{"banshee"} = "banshee";


    # BEEP MEDIA PLAYER
    # -----------------

    $known_app{"bmp"}->{"play"}         = "beep-media-player --play &";
    $known_app{"bmp"}->{"pause"}        = "beep-media-player --pause &";
    $known_app{"bmp"}->{"playpause"}    = "beep-media-player --play-pause &";
    $known_app{"bmp"}->{"prev"}         = "beep-media-player --previous &";
    $known_app{"bmp"}->{"next"}         = "beep-media-player --rew &";
    $known_app{"bmp"}->{"stop"}         = "beep-media-player --fwd &";

    $app_alias{"beep-media-play"} = "bmp";


    # EXAILE
    # ------

    $known_app{"exaile"}->{"play"}         = "exaile --play 2> /dev/null &";

    $known_app{"exaile"}->{"pause"}
    = "exaile --play-pause 2> /dev/null &";

    $known_app{"exaile"}->{"playpause"}
    = "exaile --play-pause 2> /dev/null &";

    $known_app{"exaile"}->{"prev"}         = "exaile --prev 2> /dev/null &";
    $known_app{"exaile"}->{"next"}         = "exaile --next 2> /dev/null &";
    $known_app{"exaile"}->{"stop"}         = "exaile --stop 2> /dev/null &";

    $app_alias{"exaile.py"} = "exaile";
    $app_alias{"exaile"} = "exaile";


    # GMUSICBROWSER
    # -------------

    $known_app{"gmusicbrowser"}->{"play"}
    = "gmusicbrowser -remotecmd PlayPause &";

    $known_app{"gmusicbrowser"}->{"pause"}
    = "gmusicbrowser -remotecmd PlayPause &";

    $known_app{"gmusicbrowser"}->{"playpause"}
    = "gmusicbrowser -remotecmd PlayPause &";

    $known_app{"gmusicbrowser"}->{"prev"}
    = "gmusicbrowser -remotecmd PrevSongInPlaylist &";

    $known_app{"gmusicbrowser"}->{"next"}
    = "gmusicbrowser -remotecmd NextSongInPlaylist &";

    $known_app{"gmusicbrowser"}->{"stop"}
    = "gmusicbrowser -remotecmd Stop &";

    $app_alias{"gmusicbrowser"} = "gmusicbrowser";


    # JUK
    # ---

    $known_app{"juk"}->{"play"}         = "dcop juk Player play &";
    $known_app{"juk"}->{"pause"}        = "dcop juk Player pause &";
    $known_app{"juk"}->{"playpause"}    = "dcop juk Player playPause &";
    $known_app{"juk"}->{"prev"}         = "dcop juk Player back &";
    $known_app{"juk"}->{"next"}         = "dcop juk Player forward &";
    $known_app{"juk"}->{"stop"}         = "dcop juk Player stop &";

    $app_alias{"juk"} = "juk";


    # KAFFEINE
    # --------

    $known_app{"kaffeine"}->{"play"}
    = "dcop kaffeine KaffeineIface play &";

    $known_app{"kaffeine"}->{"pause"}
    = "dcop kaffeine KaffeineIface pause &";

    $known_app{"kaffeine"}->{"playpause"}
    = "dcop kaffeine KaffeineIface pause &";

    $known_app{"kaffeine"}->{"prev"}
    = "dcop kaffeine KaffeineIface previous &";

    $known_app{"kaffeine"}->{"next"}
    = "dcop kaffeine KaffeineIface next &";

    $known_app{"kaffeine"}->{"stop"}
    = "dcop kaffeine KaffeineIface stop &";

    $app_alias{"kaffeine"} = "kaffeine";


    # KMPLAYER
    # --------

    $known_app{"kmplayer"}->{"play"}         = 1;
    $known_app{"kmplayer"}->{"pause"}        = 1;
    $known_app{"kmplayer"}->{"playpause"}    = 1;
    $known_app{"kmplayer"}->{"stop"}         = 1;


    # KSCD
    # ----

    $known_app{"kscd"}->{"play"}         = "dcop kscd CDPlayer play &";
    $known_app{"kscd"}->{"pause"}        = "dcop kscd CDPlayer play &";
    $known_app{"kscd"}->{"playpause"}    = "dcop kscd CDPlayer play &";
    $known_app{"kscd"}->{"prev"}         = "dcop kscd CDPlayer previous &";
    $known_app{"kscd"}->{"next"}         = "dcop kscd CDPlayer next &";
    $known_app{"kscd"}->{"stop"}         = "dcop kscd CDPlayer stop &";

    $app_alias{"kscd"} = "kscd";


    # LISTEN
    # ------

    $known_app{"listen"}->{"play"}         = "listen --play &";
    $known_app{"listen"}->{"pause"}        = "listen --pause &";
    $known_app{"listen"}->{"playpause"}    = "listen --play-pause &";
    $known_app{"listen"}->{"prev"}         = "listen --previous &";
    $known_app{"listen"}->{"next"}         = "listen --next &";
    $known_app{"listen"}->{"stop"}         = "listen --pause &";

    $app_alias{"listen"} = "listen";

    # MOC
    # ---

    $known_app{"moc"}->{"play"}      = "mocp --play &";
    $known_app{"moc"}->{"pause"}     = "mocp --pause &";
    $known_app{"moc"}->{"playpause"} = "mocp --toggle-pause &";
    $known_app{"moc"}->{"prev"}      = "mocp --prev &";
    $known_app{"moc"}->{"next"}      = "mocp --next &";
    $known_app{"moc"}->{"stop"}      = "mocp --stop &";

    $app_alias{"mocp"} = "moc";

    # MPD
    # ---

    $known_app{"mpd"}->{"play"}      = "mpc play 1> /dev/null &";
    $known_app{"mpd"}->{"pause"}     = "mpc pause 1> /dev/null &";
    $known_app{"mpd"}->{"playpause"} = "mpc toggle 1> /dev/null &";
    $known_app{"mpd"}->{"prev"}      = "mpc prev 1> /dev/null &";
    $known_app{"mpd"}->{"next"}      = "mpc next 1> /dev/null &";
    $known_app{"mpd"}->{"stop"}      = "mpc stop 1> /dev/null &";

    $app_alias{"mpd"} = "mpd";


    # MPLAYER
    # -------

    $known_app{"mplayer"}->{"play"}         = "echo pause > $mplayer_fifo &";
    $known_app{"mplayer"}->{"pause"}        = "echo pause > $mplayer_fifo &";
    $known_app{"mplayer"}->{"playpause"}    = "echo pause > $mplayer_fifo &";

    $known_app{"mplayer"}->{"prev"}
    = "echo \"seek -20\" > $mplayer_fifo &";

    $known_app{"mplayer"}->{"next"}
    = "echo \"seek +20\" > $mplayer_fifo &";

    $known_app{"mplayer"}->{"stop"}         = "echo quit > $mplayer_fifo &";

    # mplayer

    $app_alias{"mplayer"} = "mplayer";

    # kmplayer

    $app_alias{"kmplayer"} = "mplayer";

    # smplayer

    $app_alias{"smplayer"} = "mplayer";


    # NOATUN
    # ------

    $known_app{"noatun"}->{"play"}      = "dcop noatun Noatun play &";
    $known_app{"noatun"}->{"pause"}     = "dcop noatun Noatun playpause &";
    $known_app{"noatun"}->{"playpause"} = "dcop noatun Noatun playpause &";
    $known_app{"noatun"}->{"prev"}      = "dcop noatun Noatun back &";
    $known_app{"noatun"}->{"next"}      = "dcop noatun Noatun forward &";
    $known_app{"noatun"}->{"stop"}      = "dcop noatun Noatun stop &";

    $app_alias{"noatun"} = "noatun";


    # PYTHON
    # ------

    $app_alias{"python"} = "python";
    $app_alias{"python2.4"} = "python";
    $app_alias{"python2.5"} = "python";


    # PYTONE
    # ------

    $known_app{"pytone"}->{"play"}       = "pytonectl playerstart &";
    $known_app{"pytone"}->{"pause"}      = "pytonectl playerpause &";
    $known_app{"pytone"}->{"playpause"}  = "pytonectl playertogglepause &";
    $known_app{"pytone"}->{"prev"}       = "pytonectl playerprevious &";
    $known_app{"pytone"}->{"next"}       = "pytonectl playerforward &";
    $known_app{"pytone"}->{"stop"}       = "pytonectl playerpause &";

    $app_alias{"pytone.py"} = "pytone";
    $app_alias{"pytone"} = "pytone";


    # QUODLIBET
    # ---------

    $known_app{"quodlibet"}->{"play"}       = "quodlibet --play &";
    $known_app{"quodlibet"}->{"pause"}      = "quodlibet --pause &";
    $known_app{"quodlibet"}->{"playpause"}  = "quodlibet --play-pause &";
    $known_app{"quodlibet"}->{"prev"}       = "quodlibet --previous &";
    $known_app{"quodlibet"}->{"next"}       = "quodlibet --next &";
    $known_app{"quodlibet"}->{"stop"}       = "quodlibet --pause &";

    $app_alias{"/usr/bin/quodli"} = "quodlibet";


    # QUARK
    # -----

    $known_app{"quark"}->{"play"}       = "charm-quark play &";
    $known_app{"quark"}->{"pause"}      = "charm-quark pause &";
    $known_app{"quark"}->{"playpause"}  = "charm-quark pause &";
    $known_app{"quark"}->{"prev"}       = "charm-quark prev &";
    $known_app{"quark"}->{"next"}       = "charm-quark next &";
    $known_app{"quark"}->{"stop"}       = "charm-quark stop &";

    $app_alias{"quark"} = "quark";


    # RHYTHMBOX
    # ---------

    $known_app{"rhythmbox"}->{"play"}       = "rhythmbox-client --play &";
    $known_app{"rhythmbox"}->{"pause"}      = "rhythmbox-client --pause &";
    $known_app{"rhythmbox"}->{"playpause"}
    = "rhythmbox-client --play-pause &";

    $known_app{"rhythmbox"}->{"prev"}       = "rhythmbox-client --previous &";
    $known_app{"rhythmbox"}->{"next"}       = "rhythmbox-client --next &";
    $known_app{"rhythmbox"}->{"stop"}       = "rhythmbox-client --pause &";

    $app_alias{"rhythmbox"} = "rhythmbox";


    # SMPLAYER
    # --------

    $known_app{"smplayer"}->{"play"}       = "f play_or_pause\n";
    $known_app{"smplayer"}->{"pause"}      = "f play_or_pause\n";
    $known_app{"smplayer"}->{"playpause"}  = "f play_or_pause\n";
    $known_app{"smplayer"}->{"next"}       = "f forward2\n";
    $known_app{"smplayer"}->{"prev"}       = "f rewind2\n";
    $known_app{"smplayer"}->{"stop"}       = "f stop\n";


    # TOTEM
    # -----

    $known_app{"totem"}->{"play"}       = "totem --play &";
    $known_app{"totem"}->{"pause"}      = "totem --pause &";
    $known_app{"totem"}->{"playpause"}  = "totem --play-pause &";
    $known_app{"totem"}->{"prev"}       = "totem --previous &";
    $known_app{"totem"}->{"next"}       = "totem --next &";
    $known_app{"totem"}->{"stop"}       = "totem --pause &";

    $app_alias{"totem"} = "totem";


    # VLC
    # ---

    $known_app{"vlc"}->{"play"}
    = "wget http://localhost:$vlc_port/?control=play -O /dev/null &";

    $known_app{"vlc"}->{"pause"}
    = "wget http://localhost:$vlc_port/?control=pause -O /dev/null &";

    $known_app{"vlc"}->{"playpause"}
    = "wget http://localhost:$vlc_port/?control=pause -O /dev/null &";

    $known_app{"vlc"}->{"prev"}
    = "wget http://localhost:$vlc_port/?control=previous -O /dev/null &";

    $known_app{"vlc"}->{"next"}
    = "wget http://localhost:$vlc_port/?control=next -O /dev/null &";

    $known_app{"vlc"}->{"stop"}
    = "wget http://localhost:$vlc_port/?control=pause -O /dev/null &";

    $app_alias{"vlc"} = "vlc";


    # XINE
    # ----

    $known_app{"xine"}->{"play"}        = "xine-remote -c play &";
    $known_app{"xine"}->{"pause"}       = "xine-remote -c pause &";
    $known_app{"xine"}->{"playpause"}   = "xine-remote -c pause &";
    $known_app{"xine"}->{"stop"}        = "xine-remote -c stop &";

    $app_alias{"xine"} = "xine";


    # XMMS
    # ----

    $known_app{"xmms"}->{"play"}        = "xmms -p &";
    $known_app{"xmms"}->{"pause"}       = "xmms -u &";
    $known_app{"xmms"}->{"playpause"}   = "xmms -t &";
    $known_app{"xmms"}->{"prev"}        = "xmms -r &";
    $known_app{"xmms"}->{"next"}        = "xmms -f &";
    $known_app{"xmms"}->{"stop"}        = "xmms -s &";

    $app_alias{"xmms"} = "xmms";

    # XMMS2
    # -----

    $known_app{"xmms2"}->{"play"}        = "xmms2 play &";
    $known_app{"xmms2"}->{"pause"}       = "xmms2 pause &";
    $known_app{"xmms2"}->{"playpause"}   = "xmms2 play &";
    $known_app{"xmms2"}->{"prev"}        = "xmms2 prev &";
    $known_app{"xmms2"}->{"next"}        = "xmms2 next &";
    $known_app{"xmms2"}->{"stop"}        = "xmms2 stop &";

    $app_alias{"xmms2d"} = "xmms2";

}
