]> git.draconx.ca Git - mpdhacks.git/blobdiff - mpdmenu.pl
mpdmenu: Restructure things a bit.
[mpdhacks.git] / mpdmenu.pl
index 262cf8155dcc664ed0fe65dce6cce6c6345373d3..d22193c960fa2a778a4ef9b0227f76235830233c 100755 (executable)
 # Silly little script to generate an FVWM menu with various bits of MPD
 # status information and controls.
 #
-# License WTFPL2: Do What The Fuck You Want To Public License, version 2.
-# This is free software: you are free to do what the fuck you want to.
+# 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.
 
 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);
+use FindBin;
 
 use constant {
        MPD_MJR_MIN => 0,
-       MPD_MNR_MIN => 13,
+       MPD_MNR_MIN => 21,
        MPD_REV_MIN => 0,
 };
 
-use utf8;
-use open qw(:std :utf8);
-binmode(STDOUT, ":utf8");
-use Encode;
+my $SELF = "$FindBin::Bin/$FindBin::Script";
 
-sub cmd
-{
-       print "$_[0]\n";
+my $MUSIC = $ENV{MUSIC}    // "/srv/music";
+my $host  = $ENV{MPD_HOST} // "localhost";
+my $port  = $ENV{MPD_PORT} // "6600";
+my $sock;
+
+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, ...)
+#
+# For each music file listed, obtain a thumbnail (if any) for the cover art.
+#
+# The first argument is a hash reference to control the mode of operation;
+# it may be omitted for default options.
+#
+#   get_item_thumbnails({ small => 1 }, ...) - smaller thumbnails
+#
+# The returned list consists of strings (in the same order as the filename
+# arguments) suitable for use directly in FVWM menus; by default the filename
+# is bracketed by asterisks (e.g., "*thumbnail.png*"); in small mode it is
+# surrounded by % (e.g., "%thumbnail.png%").  If no cover art was found, the
+# empty string is returned for that file.
+sub get_item_thumbnails {
+       my @results = ();
+       my $flags = {};
+       my @opts = ();
+
+       $flags = shift if (reftype($_[0]) eq "HASH");
+       return @results unless @_;
+
+       my $c = "*";
+       if ($flags->{small}) {
+               push @opts, "--small";
+               $c = "%";
+       }
+
+       open THUMB, "-|", "$FindBin::Bin/mpdthumb.sh", @opts, "--", @_;
+       foreach (@_) {
+               my $thumb = <THUMB>;
+               chomp $thumb;
+
+               $thumb = "$c$thumb$c" if (-f $thumb);
+               push @results, $thumb;
+       }
+       close THUMB;
+       die("mpdthumb failed") if ($?);
+
+       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) = @_;
+       my $file;
+
+       $dir =~ s/\/[^\/]*$//;
+       foreach ("cover.png", "cover.jpg", "cover.tiff", "cover.bmp") {
+               if (-f "$dir/$_") {
+                       $file = "$dir/$_";
+                       last;
+               }
+       }
+       return unless defined $file;
+
+       # Follow one level of symbolic link to get to the scans directory.
+       $file = readlink($file) // $file;
+       $file = "$dir/$file" unless ($file =~ /^\//);
+       return $file;
+}
+
+# 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 = make_submenu("$menu-TopArtist",
+                                  "--artist=$entry->{Artist}");
+
+       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 @submenu = make_submenu("$menu-TopAlbum",
+                                  "--artist=$entry->{Artist}",
+                                  "--album=$entry->{Album}");
+
+       fvwm_cmd("AddToMenu", $menu,
+                fvwm_label_escape("Album:\t$entry->{Album}"),
+                @submenu);
 }
 
 # Global hash for tracking what is to be "accepted".
 my %accept = ();
 
-my $FVWM = (defined $ENV{FVWM_USERDIR}) ? $ENV{FVWM_USERDIR}
-                                        : $ENV{HOME}."/.fvwm";
-my $icons = "$FVWM/icons";
-
 # 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";
+my ($album, $artist, $title);
+
+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
+}
+
+sub print_usage {
+       my $fh = $_[1] // *STDERR;
+
+       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.
+  -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,
+
+       'album=s'    => sub { $album = $_[1]; $mode = "album"; },
+       'artist=s'   => sub { $artist = $_[1];
+                             $mode = "artist" unless $mode eq "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',
@@ -72,7 +353,65 @@ die("MPD version $1.$2.$3 insufficient.\n")
           || ($1 == MPD_MJR_MIN && $2 <  MPD_MNR_MIN)
           || ($1 == MPD_MJR_MIN && $2 == MPD_MNR_MIN && $3 < MPD_REV_MIN));
 
-if (defined $album) {
+if ($mode eq "top") {
+       my %current;
+       my %state;
+
+       $menu //= "MenuMPD";
+
+       mpd_exec("status");
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       $state{$1} = $2;
+               }
+       }
+
+       mpd_exec("currentsong");
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       add_track_metadata(\%current, $1, $2);
+               }
+       }
+
+       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]");
+       }
+
+       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") {
        # Create an album menu.
        my @playlist = ();
        my $entry;
@@ -115,13 +454,13 @@ if (defined $album) {
                $t_title  = sanitise($t_title, 0);
 
                my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\""
-                                 ." Exec exec $FVWM/scripts/mpdexec.pl"
+                                 ." Exec exec $FindBin::Bin/mpdexec.pl"
                                  ." playid %d",
                                  $t_trackno, $t_artist, $t_title, $t_id;
 
                cmd($cmd);
        }
-} elsif (defined $artist) {
+} elsif ($mode eq "artist") {
        # Create an artist menu.
        my %albums = ();
        my $file;
@@ -146,18 +485,14 @@ if (defined $album) {
 { # work around 'use locale' breaking s///i
        my $i = 0;
        use locale;
-       foreach (sort keys(%albums)) {
-               my $key      = $_;
-               my $a_album  = sanitise($key, 1);
 
-               open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
-                                        "--small", "--music", $albums{$key};
-               my $thumb = <THUMB>;
-               close THUMB;
-               die("Incompetent use of thumbnail.zsh") if ($?);
+       my @keys = sort keys %albums;
+       my @thumbs = get_item_thumbnails({ small => 1 },
+                                         map { $albums{$_} } @keys);
 
-               $thumb =~ s/\n//sg;
-               $thumb = "%$thumb%" if (-f $thumb);
+       foreach my $key (@keys) {
+               my $a_album  = sanitise($key, 1);
+               my $thumb = shift @thumbs;
 
                cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i");
 
@@ -166,7 +501,7 @@ if (defined $album) {
                cmd("DestroyFunc MakeMenuMPDArt_$i");
                cmd("AddToFunc   MakeMenuMPDArt_$i
                     + I DestroyMenu MenuMPDArt_$i
-                    + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
+                    + I -PipeRead \"exec $SELF "
                           ."--menu MenuMPDArt_$i "
                           ."--album  ".shellify($key, 1)." "
                           ."--artist ".shellify($artist, 1)."\"");
@@ -177,7 +512,7 @@ if (defined $album) {
                $i++;
        }
 } # end use locale workaround
-} elsif (defined $title) {
+} elsif ($mode eq "track") {
        # Create a title menu.
        my @titles;
        my $entry;
@@ -237,12 +572,20 @@ if (defined $album) {
 
 { # work around 'use locale' breaking s///i
        use locale;
+
+       my @thumbs = get_item_thumbnails({ small => 1 },
+                                         map { $_->{file} } @titles);
+       for (my $i = 0; $i < @titles; $i++) {
+               $titles[$i]->{thumb} = $thumbs[$i];
+       }
+
        foreach (sort titlesort @titles) {
-               my ($t_file, $t_artist, $t_title, $t_id) = (
+               my ($t_file, $t_artist, $t_title, $t_id, $thumb) = (
                        $_->{file},
                        $_->{Artist},
                        $_->{Title},
                        $_->{Id},
+                       $_->{thumb}
                );
 
                # MPD searches are case-insensitive.
@@ -251,134 +594,11 @@ if (defined $album) {
                $t_artist = sanitise($t_artist, 1);
                $t_title  = sanitise($t_title, 1);
 
-               open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
-                                        "--small", "--music", $t_file;
-               my $thumb = <THUMB>;
-               close(THUMB);
-               die("Incompetent use of thumbnail.zsh") if ($?);
-
-               $thumb =~ s/\n//sg;
-               $thumb = "%$thumb%" if (-f $thumb);
-
                cmd("AddToMenu $menu \"$thumb$t_artist - $t_title\""
-                   ." Exec exec $FVWM/scripts/mpdexec.pl"
+                   ." Exec exec $FindBin::Bin/mpdexec.pl"
                    ." 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);
-
-               open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
-                                        "--image", "--music",  $entry{file};
-               my $thumb = <THUMB>;
-               my $scan  = <THUMB>;
-               close(THUMB);
-               die("Incompetent use of thumbnail.zsh") if ($?);
-
-               $thumb =~ s/\n//sg;
-               $scan  =~ s/\n//sg;
-
-               if (-f $thumb) {
-                       cmd("AddToMenu $menu \"*$thumb*\" "
-                               ."Exec exec geeqie ".shellify($scan, 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 $FVWM/scripts/mpdexec.pl next");
-               cmd("AddToMenu $menu \"\t\tPause%$icons/pause.svg:16x16%\" "
-                       ."Exec exec $FVWM/scripts/mpdexec.pl pause");
-               cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
-                       ."Exec exec $FVWM/scripts/mpdexec.pl play");
-               cmd("AddToMenu $menu \"\t\tStop%$icons/stop.svg:16x16%\" "
-                       ."Exec exec $FVWM/scripts/mpdexec.pl stop");
-               cmd("AddToMenu $menu \"\t\tPrev%$icons/prev.svg:16x16%\" "
-                       ."Exec exec $FVWM/scripts/mpdexec.pl previous");
-       } elsif ($state eq "stop") {
-               cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
-                       ."Exec exec $FVWM/scripts/mpdexec.pl play");
-       } else {
-               die("Unknown MPD state!\n");
-       }
-
-       cmd("AddToMenu $menu \"\" Nop");
-       cmd("AddToMenu $menu \"\t\tShuffle%$icons/shuffle.svg:16x16%\" "
-               ."Exec exec $FVWM/scripts/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 $FVWM/scripts/mpdmenu.pl "
-                          ."--menu MenuMPDTitle "
-                          ."--title ".shellify($entry{Title}, 1)."\"");
-
-       cmd("DestroyFunc MakeMenuMPDAlbum");
-       cmd("AddToFunc   MakeMenuMPDAlbum
-            + I DestroyMenu MenuMPDAlbum
-            + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
-                          ."--menu MenuMPDAlbum "
-                          ."--album  ".shellify($entry{Album}, 1)." "
-                          ."--artist ".shellify($entry{Artist}, 1)."\"");
-
-       cmd("DestroyFunc MakeMenuMPDArtist");
-       cmd("AddToFunc   MakeMenuMPDArtist
-            + I DestroyMenu MenuMPDArtist
-            + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
-                          ."--menu MenuMPDArtist "
-                          ."--artist ".shellify($entry{Artist}, 1)."\"");
-
-       cmd("DestroyFunc KillMenuMPD");
-       cmd("AddToFunc   KillMenuMPD I Nop");
 }
 
 # Finished.
@@ -436,3 +656,8 @@ sub shellify
        }
        return "'$str'";
 }
+
+sub cmd
+{
+       print "$_[0]\n";
+}