#!/usr/bin/perl # # Copyright © 2008,2010,2012,2019-2020 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. package MPD; use strict; use Exporter; our ($VERSION, @ISA, @EXPORT); use IO::Socket::INET6; use IO::Socket::UNIX; $VERSION = 0; @ISA = qw(Exporter); @EXPORT = qw(); our $host = $ENV{MPD_HOST} // "localhost"; our $port = $ENV{MPD_PORT} // 6600; our $sock; our ($major, $minor, $revision); # MPD::connect([ARGS]) # # Connect to MPD based on the current settings of $MPD::host and $MPD::port. # # The following key-value arguments may optionally be specified: # # binmode => socket binmode, e.g., :utf8 or :raw. The default is :utf8. # # Text in the MPD protocol is always UTF-8 encoded but some # commands return raw binary data which can be easier to # handle in :raw mode. # # On failure, an error message is printed and undef is returned. sub connect { my %args = @_; if ($host =~ /^[@\/]/) { $host =~ s/^@/\0/; $sock = new IO::Socket::UNIX(Type => SOCK_STREAM(), Peer => $host) } else { $sock = new IO::Socket::INET6(PeerAddr => $host, PeerPort => $port, Proto => 'tcp', Timeout => 2) } die "MPD connection failed: $!\n" unless $sock; binmode($sock, $args{binmode} // ":utf8"); unless (<$sock> =~ /^OK MPD ([0-9]+)\.([0-9]+)\.([0-9]+)$/) { $sock->close(); die "MPD failed to announce version: $!\n"; } ($major, $minor, $revision) = ($1, $2, $3); return $sock; } # min_version(x, y, z) # # Returns true iff the MPD protocol version is at least x.y.z. sub min_version { my ($maj, $min, $rev) = @_; if (defined $maj) { return 1 if $maj < $major; return 0 if $maj > $major; } if (defined $min) { return 1 if $min < $minor; return 0 if $min > $minor; } if (defined $rev) { return 1 if $rev < $revision; return 0 if $rev > $revision; } return 1; } # Returns the argument (or $_ if no arguments are supplied) quoted so that it # can be presented as a single command argument to MPD at the protocol level. 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 # shell quoting. $s =~ s/\n/ /g; if ($s =~ /[ \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 exec { my $cmd = join(' ', map { MPD::escape } @_); print $sock "$cmd\n"; } # Submit a command to the MPD server and wait for it to complete. # Intended for simple cases where the command output is unneeded. sub run { MPD::exec(@_); while (<$sock>) { last if (/^OK/); die($_) if (/^ACK/); } } 1;