The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

use v5.10.2;
use strict;
use warnings;
use Net::DBus;
use Net::DBus::Reactor;
use Net::DBus::Dumper;
use Log::Any::Adapter;
use Log::Dispatch;
use Net::LCDproc;

no if $] >= 5.018, 'warnings', 'experimental::smartmatch';

my $log;
my $mediaplayer;
my $mpris;
my $lcdproc;
my $screen;
my $widget = {};

# map mpris playback states to icon names
my $icon = {
    Playing => 'PLAY',
    Paused  => 'PAUSE',
    Stopped => 'STOP',
};

sub start_logging {
    my $log =
      Log::Dispatch->new(outputs => [['Syslog', min_level => 'debug',]]);
    Log::Any::Adapter->set('Dispatch', dispatcher => $log);
    return $log;
}

sub get_metadata {

    my $raw_metadata =
      $mpris->Get('org.mpris.MediaPlayer2.Player', "Metadata");

    my $metadata = {
        artist     => ($raw_metadata->{'xesam:artist'}->[0] || 'unknown'),         # I am lazy
        album      => ($raw_metadata->{'xesam:album'} || 'unknown'),
        title      => $raw_metadata->{'xesam:title'},
        tracknum   => $raw_metadata->{'xesam:trackNumber'},
        length_sec => $raw_metadata->{'mpris:length'} / 1_000_000,
        playbackstatus =>
          $mpris->Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus'),
    };

    if ($metadata->{playbackstatus} eq 'Stopped') {
        $metadata->{artist_album_str} = 'Not Playing';
        $metadata->{track_str}        = '';
    } else {

        # space at the end because using marquee
        $metadata->{artist_album_str} = sprintf '%s :: %s :: ',
          $metadata->{artist}, $metadata->{album};
        $metadata->{track_str} = sprintf '%d. %s ',
          $metadata->{tracknum} // 0, $metadata->{title};
        $metadata->{position_sec} =
          $mpris->Get('org.mpris.MediaPlayer2.Player', "Position") / 1_000_000;
    }

    return $metadata;
}

sub get_mpris {
    my $bus = Net::DBus->session;

    # find a music player
    my $service = $bus->get_service("org.freedesktop.DBus");
    my $dbus    = $service->get_object("/org/freedesktop/DBus");
    foreach (@{$dbus->ListNames}) {
        next if not m/org\.mpris\.MediaPlayer2/;
        $log->info("Trying '$_'");
        my $mpris_service = $bus->get_service($_);
        $mpris = $mpris_service->get_object('/org/mpris/MediaPlayer2');
        $mediaplayer = $mpris->Get('org.mpris.MediaPlayer2', 'Identity');
        $log->info("Using $mediaplayer");
        last;
    }
}

sub calc_bar_length {
    my $metadata = shift;
    
    if (!$metadata->{length_sec}) {
        return 0;
    }

    if (!$metadata->{position_sec}) {
        return 0;
    }

    my $total_screen_length = $lcdproc->width * $lcdproc->cell_width;

    my $curr_pc = $metadata->{position_sec} / $metadata->{length_sec} * 100;
    $log->debug(sprintf 'Track progress: %.2f%%', $curr_pc);

    my $length = int($total_screen_length * ($curr_pc / 100));
    return $length;
}

sub setup_screen {

    my $server = shift @ARGV || 'localhost';
    $lcdproc = Net::LCDproc->new(server => $server);
    $screen = Net::LCDproc::Screen->new(id => 'mediaplayer');
    $lcdproc->add_screen($screen);

    my $metadata = get_metadata;

    $widget->{artist_album} = Net::LCDproc::Widget::Scroller->new(
        id        => 'artist_album',
        left      => 1,
        top       => 1,
        right     => $lcdproc->width,
        bottom    => 1,
        speed     => 5,
        direction => 'm',
        text      => $metadata->{artist_album_str},
    );
    $screen->add_widget($widget->{artist_album});

    $widget->{track} = Net::LCDproc::Widget::Scroller->new(
        id        => 'track',
        left      => 1,
        top       => 2,
        right     => $lcdproc->width,
        bottom    => 2,
        speed     => 5,
        direction => 'm',
        text      => $metadata->{track_str},
    );
    $screen->add_widget($widget->{track});

    $widget->{icon} = Net::LCDproc::Widget::Icon->new(
        id       => 'icon',
        x        => $lcdproc->width / 2,
        y        => 3,
        iconname => $icon->{$metadata->{playbackstatus}},
    );
    $screen->add_widget($widget->{icon});

    $widget->{progress} = Net::LCDproc::Widget::HBar->new(
        id     => 'progress',
        x      => 1,
        y      => 4,
        length => calc_bar_length($metadata),
    );
    $screen->add_widget($widget->{progress});

}

sub update_status {
    my ($changed) = shift;

    # TODO if the metadata changes, it get passed from dbus
    my $metadata = get_metadata;
    my $playbackstatus =
      $mpris->Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus');

    given ($playbackstatus) {
        when ('Stopped') {
            $widget->{artist_album}->text('Nothing playing');
        }
        when ('Paused') {

            # icon
        }
        default {
            my $length = calc_bar_length($metadata);
            $widget->{progress}->length($length);
        }
    }

    if ($changed) {
        $log->debug('Something changed so updating EVERYTHING');

        if ($metadata) {
            $widget->{track}->text($metadata->{track_str});
            $widget->{artist_album}->text($metadata->{artist_album_str});
            $widget->{icon}->iconname($icon->{$metadata->{playbackstatus}}),;
        }
    }

    $lcdproc->update;
}

$log = start_logging;
get_mpris;
setup_screen;
say "Starting main loop. Check your syslog";

my $reactor = Net::DBus::Reactor->main;

# a better program might care *what* changed
$mpris->connect_to_signal('PropertiesChanged', \&update_status);
my $timer = $reactor->add_timeout(1000, \&update_status);

$reactor->run;