use strict;
-use Getopt::Long;
+use utf8;
+
+use Encode qw(decode encode);
+use Encode::Locale qw(decode_argv);
+decode_argv(Encode::FB_CROAK);
+binmode(STDOUT, ":utf8");
+
use IO::Socket::INET6;
+use Getopt::Long qw(:config gnu_getopt);
use Scalar::Util qw(reftype);
+use List::Util qw(any max);
use FindBin;
use constant {
MPD_REV_MIN => 0,
};
-use utf8;
-use open qw(:std :utf8);
-binmode(STDOUT, ":utf8");
-use Encode;
-
my $SELF = "$FindBin::Bin/$FindBin::Script";
-my $MUSIC = $ENV{MUSIC} // "/srv/music";
+
+my $MUSIC = $ENV{MUSIC} // "/srv/music";
+my $host = $ENV{MPD_HOST} // "localhost";
+my $port = $ENV{MPD_PORT} // "6600";
+my $sock;
+
+my ($albumid, $title);
+my %artistids;
+my $menu;
+my $mode = "top";
+
+# Quotes the argument so that it is presented as a single argument to MPD
+# at the protocol level. This also works OK for most FVWM arguments.
+sub escape {
+ my $s = @_[0] // $_;
+
+ # No way to encode literal newlines in the protocol, so we
+ # convert any newlines in the arguments into a space, which
+ # can help with quoting.
+ $s =~ s/\n/ /g;
+
+ if (/[ \t\\"]/) {
+ $s =~ s/[\\"]/\\$&/g;
+ return "\"$s\"";
+ }
+
+ $s =~ s/^\s*$/"$&"/;
+ return $s;
+}
+
+# Submit a command to the MPD server; each argument to this function
+# is quoted and sent as a single argument to MPD.
+sub mpd_exec {
+ my $cmd = join(' ', map { escape } @_);
+
+ print $sock "$cmd\n";
+}
+
+sub fvwm_cmd_unquoted {
+ print join(' ', @_), "\n";
+}
+
+sub fvwm_cmd {
+ fvwm_cmd_unquoted(map { escape } @_);
+}
+
+# Quotes the argument in such a way that it is passed unadulterated by
+# both FVWM and the shell to a command as a single argument (for use as
+# an # argument for e.g., the Exec or PipeRead FVWM commands).
+#
+# The result must be used with fvwm_cmd_unquoted;
+sub fvwm_shell_literal {
+ my $s = @_[0] // $_;
+
+ $s =~ s/\$/\$\$/g;
+ if ($s =~ /[' \t]/) {
+ $s =~ s/'/'\\''/g;
+ return "'$s'";
+ }
+ $s =~ s/^\s*$/'$&'/;
+ return "$s";
+}
+
+# Escapes metacharacters in the argument used in FVWM menu labels. The
+# string must still be quoted (e.g., by using fvwm_cmd).
+sub fvwm_label_escape {
+ my @tokens = split /\t/, $_[0];
+ @tokens[0] =~ s/&/&&/g;
+ my $ret = join "\t", @tokens;
+ $ret =~ s/[\$@%^*]/$&$&/g;
+ return $ret;
+}
+
+# make_submenu(name, [args ...])
+#
+# Creates a submenu (with the specified name) constructed by invoking this
+# script with the given arguments. Returns a list that can be passed to
+# fvwm_cmd to display the menu.
+sub make_submenu {
+ my $name = shift;
+ $name =~ s/-/_/g;
+ unshift @_, ("exec", $SELF, "--menu=$name");
+
+ fvwm_cmd("DestroyFunc", "Make$name");
+ fvwm_cmd("AddToFunc", "Make$name");
+ fvwm_cmd("+", "I", "DestroyMenu", $name);
+
+ fvwm_cmd("DestroyMenu", $name);
+ fvwm_cmd("AddToMenu", $name, "DynamicPopupAction", "Make$name");
+ fvwm_cmd("AddToFunc", "KillMenuMPD", "I", "DestroyMenu", $name);
+
+ fvwm_cmd("DestroyFunc", "Make$name");
+ fvwm_cmd("AddToFunc", "Make$name");
+ fvwm_cmd("+", "I", "DestroyMenu", $name);
+ fvwm_cmd("+", "I", "-PipeRead",
+ join(' ', map { fvwm_shell_literal } @_));
+ fvwm_cmd("AddToFunc", "KillMenuMPD", "I", "DestroyFunc", "Make$name");
+
+ return ("Popup", $name);
+}
# get_item_thumbnails({ options }, file, ...)
# get_item_thumbnails(file, ...)
return @results;
}
+# add_track_metadata(hashref, key, value)
+#
+# Inserts the given key into the referenced hash; if the key already exists
+# in the hash then the hash element is converted to an array reference (if
+# it isn't already) and the value is appended to that array.
+sub add_track_metadata {
+ my ($entry, $key, $value) = @_;
+
+ if (exists($entry->{$key})) {
+ my $ref = $entry->{$key};
+
+ if (reftype($ref) ne "ARRAY") {
+ return if ($ref eq $value);
+
+ $ref = [$ref];
+ $entry->{$key} = $ref;
+ }
+
+ push(@$ref, $value) unless (any {$_ eq $value} @$ref);
+ } else {
+ $entry->{$key} = $value;
+ }
+}
+
+# get_track_metadata(hashref, key)
+#
+# Return the values associated with the given metadata key as a list.
+sub get_track_metadata {
+ my ($entry, $key) = @_;
+
+ return () unless (exists($entry->{$key}));
+
+ my $ref = $entry->{$key};
+ return @$ref if (reftype($ref) eq "ARRAY");
+ return $ref
+}
+
# Given a music filename, search for the cover art in the same directory.
sub mpd_cover_filename {
my ($dir) = @_;
return $file;
}
-sub cmd
-{
- print "$_[0]\n";
+# Generate the cover art entry in the top menu.
+sub top_track_cover {
+ my ($entry) = @_;
+
+ ($entry->{thumb}) = get_item_thumbnails($entry->{file});
+ print "$entry->{thumb}\n";
+ if ($entry->{thumb}) {
+ my $file = "$MUSIC/$entry->{file}";
+ my $cover = mpd_cover_filename($file);
+
+ $cover = fvwm_shell_literal($cover // $file);
+ fvwm_cmd_unquoted("AddToMenu", escape($menu),
+ escape($entry->{thumb}),
+ "Exec", "exec", "geeqie", $cover);
+ }
+}
+
+# Generate the "Title:" entry in the top menu.
+sub top_track_title {
+ my ($entry) = @_;
+
+ my @submenu = make_submenu("$menu-TopTrack",
+ "--title=$entry->{Title}");
+
+ fvwm_cmd("AddToMenu", $menu,
+ fvwm_label_escape("Title:\t$entry->{Title}"),
+ @submenu);
+}
+
+# Generate the "Artist:" entry in the top menu.
+sub top_track_artist {
+ my ($entry) = @_;
+ my @submenu;
+
+ # TODO: multi-artist tracks should get multiple artist menus; for now
+ # just combine the releases from all artists.
+ my @mbids = get_track_metadata($entry, "MUSICBRAINZ_ARTISTID");
+ if (@mbids) {
+ @submenu = make_submenu("$menu-TopArtist",
+ map { "--artist-id=$_" } @mbids);
+ }
+
+ fvwm_cmd("AddToMenu", $menu,
+ fvwm_label_escape("Artist:\t$entry->{Artist}"),
+ @submenu);
+}
+
+# Generate the "Album:" entry in the top menu.
+sub top_track_album {
+ my ($entry) = @_;
+ my @submenu;
+
+ my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID");
+ @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid") if $mbid;
+
+ fvwm_cmd("AddToMenu", $menu,
+ fvwm_label_escape("Album:\t$entry->{Album}"),
+ @submenu);
+}
+
+# Given a release MBID, return a hash reference containing all its
+# associated tracks in the MPD database. The hash keys are filenames.
+sub get_tracks_by_release_mbid {
+ my ($mbid) = @_;
+ my %matches;
+ my $entry;
+
+ return \%matches unless ($mbid);
+ mpd_exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ if ($1 eq "file") {
+ if (exists($matches{$2})) {
+ $entry = $matches{$2};
+ } else {
+ $entry = {};
+ $matches{$2} = $entry;
+ }
+ }
+
+ add_track_metadata($entry, $1, $2);
+ }
+ }
+
+ return \%matches;
+}
+
+# Given an artist MBID, return a hash reference containing associated
+# releases in the MPD database. The hash keys are release MBIDs.
+#
+# Since MPD returns results on a per-track basis, each entry in the
+# hash has the metadata for one unspecified track from that release.
+sub get_releases_by_artist_mbid {
+ my %releases;
+ my $entry;
+
+ foreach my $mbid (@_) {
+ mpd_exec("search", "(MUSICBRAINZ_ARTISTID == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ if ($1 eq "file") {
+ $entry = {};
+ } elsif ($1 eq "MUSICBRAINZ_ALBUMID") {
+ $releases{$2} //= $entry;
+ }
+
+ add_track_metadata($entry, $1, $2);
+ }
+ }
+ }
+
+ return \%releases;
+}
+
+# Given a filename, return the IDs (if any) for that file in the
+# current MPD play queue.
+sub get_ids_by_filename {
+ my ($file) = @_;
+ my @results = ();
+
+ mpd_exec("playlistfind", "file", $file);
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ push @results, $2 if ($1 eq "Id");
+ }
+ }
+
+ return @results;
+}
+
+# albumsort(matches, a, b)
+#
+# Sort hash keys (a, b) by disc/track number for album menus.
+sub albumsort {
+ my ($matches, $a, $b) = @_;
+
+ return $matches->{$a}->{Disc} <=> $matches->{$b}->{Disc}
+ || $matches->{$a}->{Track} <=> $matches->{$b}->{Track}
+ || $a cmp $b;
+}
+
+# datesort(matches, a, b)
+#
+# Sort hash keys (a, b) by release date
+sub datesort {
+ my ($matches, $a, $b) = @_;
+
+ return $matches->{$a}->{Date} cmp $matches->{$b}->{Date}
+ || $a cmp $b;
+}
+
+# menu_trackname(entry)
+#
+# Format the track name for display in an FVWM menu, where entry
+# is a hash reference containing the track metadata.
+sub menu_trackname {
+ my ($entry) = @_;
+ my $fmt = "$entry->{trackfmt}$entry->{Artist} - $entry->{Title}";
+ return "$entry->{thumb}" . fvwm_label_escape($fmt);
}
-# Global hash for tracking what is to be "accepted".
-my %accept = ();
+sub print_version {
+ print <<EOF
+mpdmenu.pl 0.8
+Copyright © 2019 Nick Bowler
+License GPLv3+: GNU General Public License version 3 or any later version.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+EOF
+}
-my $FVWM = (defined $ENV{FVWM_USERDIR}) ? $ENV{FVWM_USERDIR}
- : $ENV{HOME}."/.fvwm";
-my $icons = "$FVWM/icons";
+sub print_usage {
+ my $fh = $_[1] // *STDERR;
-# Default values for stuff.
-my ($album, $artist, $title, $menu) = (undef, undef, undef, undef);
-my $host = (defined $ENV{MPD_HOST}) ? $ENV{MPD_HOST} : "localhost";
-my $port = (defined $ENV{MPD_PORT}) ? $ENV{MPD_PORT} : "6600";
+ print $fh "Usage: $0 [options]\n";
+ print "Try $0 --help for more information.\n" unless (@_ > 0);
+}
+
+sub print_help {
+ print_usage(*STDOUT);
+ print <<EOF
+This is "mpdmenu": a menu-based MPD client for FVWM.
+
+Options:
+ -h, --host=HOST Connect to the MPD server on HOST, overriding defaults.
+ -p, --port=PORT Connect to the MPD server on PORT, overriding defaults.
+ -m, --menu=NAME Set the name of the generated menu.
+ --album-id=MBID Generate a menu for the given release MBID.
+ --artist-id=MBID Generate a menu for the given artist MBID.
+ -V, --version Print a version message and then exit.
+ -H, --help Print this message and then exit.
+EOF
+}
GetOptions(
- 'host|h=s' => \&host, # Host that MPD is running on.
- 'port|p=s' => \&port, # Port that MPD is listening on.
- 'menu|m=s' => \$menu, # Name of the menu to create.
- 'album=s' => \$album, # Album to get tracks from
- 'artist=s' => \$artist, # Artist to limit results to
- 'title=s' => \$title, # Title to create menu for
-);
-
-$album = decode_utf8($album) if defined($album);
-$artist = decode_utf8($artist) if defined($artist);
-$title = decode_utf8($title) if defined($title);;
+ 'host|h=s' => \$host,
+ 'port|p=s' => \$port,
+ 'menu|m=s' => \$menu,
+
+ 'artist-id=s' => sub { $artistids{$_[1]} = 1; $mode = "artist"; },
+ 'album-id=s' => sub { $albumid = $_[1]; $mode = "album"; },
+ 'title=s' => sub { $title = $_[1]; $mode = "track"; },
+
+ 'V|version' => sub { print_version(); exit },
+ 'H|help' => sub { print_help(); exit },
+) or do { print_usage; exit 1 };
# Connect to MPD.
-my $sock = new IO::Socket::INET6(
+$sock = new IO::Socket::INET6(
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
|| ($1 == MPD_MJR_MIN && $2 < MPD_MNR_MIN)
|| ($1 == MPD_MJR_MIN && $2 == MPD_MNR_MIN && $3 < MPD_REV_MIN));
-if (defined $album) {
- # Create an album menu.
- my @playlist = ();
- my $entry;
+if ($mode eq "top") {
+ my %current;
+ my %state;
- $menu = "MenuMPDAlbum" unless defined $menu;
+ $menu //= "MenuMPD";
- $album =~ s/"/\\"/g;
- print $sock "playlistfind album \"$album\"\n";
+ mpd_exec("status");
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
if (/^(\w+): (.*)$/) {
- if ($1 eq "file") {
- if (keys(%$entry) > 0) {
- addalbumentry(\@playlist, $entry)
- }
-
- $entry = {};
- }
-
- $entry->{$1} = $2;
+ $state{$1} = $2;
}
}
- addalbumentry(\@playlist, $entry) if (keys(%$entry) > 0);
- die("No tracks found.\n") if (!@playlist);
- foreach (sort albumsort @playlist) {
- my ($t_file, $t_trackno, $t_artist, $t_title, $t_id) = (
- $_->{file},
- $_->{Track},
- $_->{Artist},
- $_->{Title},
- $_->{Id},
- );
-
- next if (defined $artist && !$accept{albumdir($t_file)});
-
- $t_artist = sanitise($t_artist, 0);
- $t_title = sanitise($t_title, 0);
-
- my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\""
- ." Exec exec $FindBin::Bin/mpdexec.pl"
- ." playid %d",
- $t_trackno, $t_artist, $t_title, $t_id;
-
- cmd($cmd);
- }
-} elsif (defined $artist) {
- # Create an artist menu.
- my %albums = ();
- my $file;
- my $quoteartist = $artist;
-
- $menu = "MenuMPDArtist" unless defined $menu;
-
- $quoteartist =~ s/"/\\"/g;
- print $sock "playlistfind artist \"$quoteartist\"\n";
+ mpd_exec("currentsong");
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
if (/^(\w+): (.*)$/) {
- $file = $2 if ($1 eq "file");
- $albums{$2} = $file if ($1 eq "Album");
+ add_track_metadata(\%current, $1, $2);
}
}
- die("No albums found.\n") if (!keys(%albums));
+ my $playstate = $state{state} eq "play" ? "Playing"
+ : $state{state} eq "stop" ? "Stopped"
+ : $state{state} eq "pause" ? "Paused"
+ : "Unknown";
+ fvwm_cmd("AddToMenu", $menu, $playstate, "Title");
+
+ if (exists($current{file})) {
+ top_track_cover(\%current);
+ top_track_title(\%current);
+ top_track_artist(\%current);
+ top_track_album(\%current);
+ } else {
+ fvwm_cmd("AddToMenu", $menu, "[current track unavailable]");
+ }
-{ # work around 'use locale' breaking s///i
- my $i = 0;
- use locale;
+ if ($state{state} =~ /^p/) {
+ my $pp = $state{state} eq "pause" ? "lay" : "ause";
+
+ fvwm_cmd("AddToMenu", $menu, "", "Nop");
+ fvwm_cmd("AddToMenu", $menu, "Next%next.svg:16x16%",
+ "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "next");
+ fvwm_cmd("AddToMenu", $menu, "P$pp%p$pp.svg:16x16%",
+ "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "p$pp");
+ fvwm_cmd("AddToMenu", $menu, "Stop%stop.svg:16x16%",
+ "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "stop");
+ fvwm_cmd("AddToMenu", $menu, "Prev%prev.svg:16x16%",
+ "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "previous");
+ } elsif ($state{state} eq "stop") {
+ fvwm_cmd("AddToMenu", $menu, "", "Nop");
+ fvwm_cmd("AddToMenu", $menu, "Play%play.svg:16x16%",
+ "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "play");
+ }
+} elsif ($mode eq "album") {
+ my $matches = get_tracks_by_release_mbid($albumid);
+ my @notqueued = ();
+
+ $menu //= "MenuMPDAlbum";
+
+ my $track_max = max(map { $_->{Track} } values %$matches);
+ my $disc_max = max(map { $_->{Disc} } values %$matches);
+
+ # CDs have a max of 99 tracks and I hope 100+-disc-releases
+ # don't exist so this is fine.
+ my $track_digits = $track_max >= 10 ? 2 : 1;
+ my $disc_digits = $disc_max > 1 ? $disc_max >= 10 ? 2 : 1 : 0;
+ my $currentdisc;
+
+ fvwm_cmd("AddToMenu", $menu);
+ fvwm_cmd("+", "Release not found", "Title") unless keys %$matches;
+ foreach my $file (sort { albumsort($matches, $a, $b) } keys %$matches) {
+ my $entry = $matches->{$file};
+
+ # Format disc/track numbers
+ $entry->{trackfmt} = sprintf("%*.*s%.*s%*d\t",
+ $disc_digits, $disc_digits, $entry->{Disc},
+ $disc_digits, "-",
+ $track_digits, $entry->{Track});
+ $entry->{trackfmt} =~ s/ /\N{U+2007}/g;
+
+ unless (exists $entry->{Id}) {
+ my ($id) = get_ids_by_filename($file);
+ if (defined $id) {
+ $entry->{Id} = $id;
+ } else {
+ push @notqueued, $entry;
+ next;
+ }
+ }
- my @keys = sort keys %albums;
- my @thumbs = get_item_thumbnails({ small => 1 },
- map { $albums{$_} } @keys);
+ if (defined $currentdisc && $currentdisc != $entry->{Disc}) {
+ fvwm_cmd("+", "", "Nop");
+ }
+ $currentdisc = $entry->{Disc};
- foreach my $key (@keys) {
- my $a_album = sanitise($key, 1);
- my $thumb = shift @thumbs;
+ fvwm_cmd("+", menu_trackname($entry), "Exec",
+ "exec", "$FindBin::Bin/mpdexec.pl",
+ "playid", $entry->{Id});
+ }
- cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i");
+ fvwm_cmd("+", "Not in play queue", "Title") if @notqueued;
+ foreach my $entry (@notqueued) {
+ fvwm_cmd("+", menu_trackname($entry));
+ }
+} elsif ($mode eq "artist") {
+ # Create an artist menu.
+ my $matches = get_releases_by_artist_mbid(keys %artistids);
+ my $entry;
- cmd("AddToMenu MenuMPDArt_$i DynamicPopUpAction MakeMenuMPDArt_$i");
+ $menu //= "MenuMPDArtist";
- cmd("DestroyFunc MakeMenuMPDArt_$i");
- cmd("AddToFunc MakeMenuMPDArt_$i
- + I DestroyMenu MenuMPDArt_$i
- + I -PipeRead \"exec $SELF "
- ."--menu MenuMPDArt_$i "
- ."--album ".shellify($key, 1)." "
- ."--artist ".shellify($artist, 1)."\"");
+ my @mbids = sort { datesort($matches, $a, $b) } keys %$matches;
+ my @files = map { $matches->{$_}->{file} } @mbids;
+ my @thumbs = get_item_thumbnails({ small => 1 }, @files);
+ fvwm_cmd("AddToMenu", $menu, "No releases found", "Title") unless @mbids;
- cmd("AddToFunc KillMenuMPD I DestroyMenu MenuMPDArt_$i");
- cmd("AddToFunc KillMenuMPD I DestroyFunc MakeMenuMPDArt_$i");
+ foreach my $mbid (@mbids) {
+ my $entry = $matches->{$mbid};
+ my $thumb = shift @thumbs;
- $i++;
+ my @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid");
+ fvwm_cmd("AddToMenu", $menu,
+ $thumb . fvwm_label_escape($entry->{Album}),
+ @submenu);
}
-} # end use locale workaround
-} elsif (defined $title) {
+} elsif ($mode eq "track") {
# Create a title menu.
my @titles;
my $entry;
." playid $t_id");
}
} # end use locale workaround
-} else {
- # Make MPD base menu
- my ($state, $songid) = (undef, undef);
- my %entry = ();
-
- $menu = "MenuMPD" unless defined $menu;
-
- print $sock "status\n";
- while (<$sock>) {
- last if (/^OK/);
- die($_) if (/^ACK/);
-
- if (/^(\w+): (.*)$/) {
- $state = $2 if ($1 eq "state");
- $songid = $2 if ($1 eq "songid");
- }
- }
- die("Failed status query\n") unless (defined $state);
-
- cmd("AddToMenu $menu Playing Title") if ($state eq "play");
- cmd("AddToMenu $menu Paused Title") if ($state eq "pause");
- cmd("AddToMenu $menu Stopped Title") if ($state eq "stop");
-
- if (defined $songid) {
- print $sock "playlistid $songid\n";
- while (<$sock>) {
- last if (/^OK/);
- die($_) if (/^ACK/);
-
- if (/^(\w+): (.*)$/) {
- $entry{$1} = $2;
- }
- }
- die("Failed data query\n") unless (keys(%entry) > 0);
-
- my ($thumb) = get_item_thumbnails($entry{file});
- if ($thumb) {
- my $cover = mpd_cover_filename("$MUSIC/$entry{file}");
-
- cmd("AddToMenu $menu \"$thumb\" "
- ."Exec exec geeqie ".shellify($cover, 0));
- }
-
- cmd("AddToMenu $menu \"Title: ".sanitise($entry{Title}, 0)
- ."\" Popup MenuMPDTitle");
- cmd("AddToMenu $menu \"Artist: ".sanitise($entry{Artist}, 0)
- ."\" Popup MenuMPDArtist");
- cmd("AddToMenu $menu \"Album: ".sanitise($entry{Album}, 0)
- ."\" Popup MenuMPDAlbum");
- cmd("AddToMenu $menu \"\" Nop");
- } else {
- cmd("AddToMenu $menu \"<Song info unavailable>\"");
- cmd("AddToMenu $menu \"\" Nop");
- }
-
- if ($state eq "play" || $state eq "pause") {
- cmd("AddToMenu $menu \"\t\tNext%$icons/next.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl next");
- cmd("AddToMenu $menu \"\t\tPause%$icons/pause.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl pause");
- cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl play");
- cmd("AddToMenu $menu \"\t\tStop%$icons/stop.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl stop");
- cmd("AddToMenu $menu \"\t\tPrev%$icons/prev.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl previous");
- } elsif ($state eq "stop") {
- cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl play");
- } else {
- die("Unknown MPD state!\n");
- }
-
- cmd("AddToMenu $menu \"\" Nop");
- cmd("AddToMenu $menu \"\t\tShuffle%$icons/shuffle.svg:16x16%\" "
- ."Exec exec $FindBin::Bin/mpdexec.pl shuffle");
-
- cmd("DestroyMenu MenuMPDTitle");
- cmd("AddToMenu MenuMPDTitle DynamicPopUpAction MakeMenuMPDTitle");
- cmd("DestroyMenu MenuMPDArtist");
- cmd("AddToMenu MenuMPDArtist DynamicPopUpAction MakeMenuMPDArtist");
- cmd("DestroyMenu MenuMPDAlbum");
- cmd("AddToMenu MenuMPDAlbum DynamicPopUpAction MakeMenuMPDAlbum");
-
- cmd("DestroyFunc MakeMenuMPDTitle");
- cmd("AddToFunc MakeMenuMPDTitle
- + I DestroyMenu MenuMPDTitle
- + I -PipeRead \"exec $SELF "
- ."--menu MenuMPDTitle "
- ."--title ".shellify($entry{Title}, 1)."\"");
-
- cmd("DestroyFunc MakeMenuMPDAlbum");
- cmd("AddToFunc MakeMenuMPDAlbum
- + I DestroyMenu MenuMPDAlbum
- + I -PipeRead \"exec $SELF "
- ."--menu MenuMPDAlbum "
- ."--album ".shellify($entry{Album}, 1)." "
- ."--artist ".shellify($entry{Artist}, 1)."\"");
-
- cmd("DestroyFunc MakeMenuMPDArtist");
- cmd("AddToFunc MakeMenuMPDArtist
- + I DestroyMenu MenuMPDArtist
- + I -PipeRead \"exec $SELF "
- ."--menu MenuMPDArtist "
- ."--artist ".shellify($entry{Artist}, 1)."\"");
-
- cmd("DestroyFunc KillMenuMPD");
- cmd("AddToFunc KillMenuMPD I Nop");
}
# Finished.
return $_;
}
-sub addalbumentry
-{
- my ($playlist, $entry) = @_;
-
- push(@$playlist, $entry);
-
- if (defined $artist && $artist eq $entry->{Artist}) {
- my $albumdir = albumdir($entry->{file});
- $accept{$albumdir} = 1;
- }
-}
-
-sub albumdir
-{
- my $file = $_[0];
-
- $file =~ s:(/Disk [0-9]+[^/]*)?/[^/]*$::;
- return $file
-}
-
-sub albumsort
-{
- return ($a->{Disc} <=> $b->{Disc}) if ($a->{Disc} != $b->{Disc});
- return ($a->{Track} <=> $b->{Track});
-}
-
sub titlesort
{
return ($a->{Album} cmp $b->{Album}) if($a->{Album} ne $b->{Album});
return ($a->{Title} cmp $b->{Title});
}
-sub shellify
+sub cmd
{
- my ($str, $quoted) = @_;
- $str =~ s/'/'\\''/g;
- if ($quoted) {
- $str =~ s/\\/\\\\/g;
- $str =~ s/"/\\"/g;
- }
- return "'$str'";
+ print "$_[0]\n";
}