+ if ($?) {
+ my $st;
+ if ($? >= 256) { $st = sprintf "rc = %d", $? >> 8; }
+ else { $st = sprintf "signal %d", $?; }
+ die "$_[0] failed ($st)";
+ }
+}
+
+sub notify ($$) {
+ my ($head, $body) = @_;
+
+ $body =~ s:\&:&:g;
+ $body =~ s:\<:<:g;
+ $body =~ s:\>:>:g;
+
+ ##print "****************\n$head\n\n$body\n"; return;
+
+ run_discard_output "notify-send",
+ "-c", "DisOrder", "-i", "audio-volume-high", "-t", "5000",
+ $head, $body;
+}
+
+sub try_unlink ($) {
+ my ($f) = @_;
+ eval { unlink $f; };
+ die $@ if $@ and $@->errno != ENOENT;
+}
+
+###--------------------------------------------------------------------------
+### Locking protocol.
+
+my $LKFILE = "$C{lockdir}/disorder-notify-$VARIANT.lock";
+my $LKFH;
+
+sub locked_by () {
+
+ ## Try to open the lock file. If it's not there, then obviously it's not
+ ## locked.
+ my $fh;
+ eval { open $fh, "<", $LKFILE; };
+ if ($@) {
+ return undef if $@->errno == ENOENT;
+ die $@;
+ }
+
+ ## Take out a non-exclusive lock on the lock file.
+ my $lk = new File::FcntlLock;
+ $lk->l_type(F_RDLCK); $lk->l_whence(SEEK_SET);
+ $lk->l_start(0); $lk->l_len(0);
+ if ($lk->lock($fh, F_SETLK)) { close $fh; return undef; }
+
+ ## Read the pid of the current lock-holder.
+ chomp (my $pid = (readline $fh) // "<unknown>");
+ close $fh;
+ return $pid;
+}
+
+sub claim_lock () {
+ sysopen my $fh, $LKFILE, O_CREAT | O_WRONLY;
+
+ my $lk = new File::FcntlLock;
+ $lk->l_type(F_WRLCK); $lk->l_whence(SEEK_SET);
+ $lk->l_start(0); $lk->l_len(0);
+ if (!$lk->lock($fh, F_SETLK)) {
+ return undef if $! == EAGAIN;
+ die "failed to lock `$LKFILE': $!";
+ }
+
+ truncate $fh, 0;
+ print $fh "$$\n";
+ flush $fh;
+ $LKFH = $fh;
+ 1;
+}
+
+###--------------------------------------------------------------------------
+### DisOrder utilities.
+
+sub get_state0 ($) {
+ my ($sk) = @_;
+ my %st = ();
+
+ LINE: for (;;) {
+ my @f = split_fields readline $sk;
+ if ($f[1] ne "state") { last LINE; }
+ elsif ($f[2] eq "enable_random") { $st{random} = 1; }
+ elsif ($f[2] eq "disable_random") { $st{random} = 0; }
+ elsif ($f[2] eq "enable_play") { $st{play} = 1; }
+ elsif ($f[2] eq "disable_play") { $st{play} = 0; }
+ elsif ($f[2] eq "resume") { $st{pause} = 0; }
+ elsif ($f[2] eq "pause") { $st{pause} = 1; }
+ }
+ return \%st;
+}
+
+my $CONF = undef;
+
+sub configured_connection (;$) {
+ my ($quietp) = @_;
+ $CONF //= load_config $C{config};
+ return connect_to_server %$CONF, $quietp // 0;
+}
+
+sub get_state () {
+ my $sk = configured_connection;
+ send_command0 $sk, "log";
+ my $st = get_state0 $sk;
+ close $sk;
+ return $st;
+}
+
+sub decode_track_name ($\%) {
+ my ($sk, $info) = @_;
+ return unless exists $info->{track};
+ my $track = $info->{track};
+ for my $i ("artist", "album", "title") {
+ my @f = split_fields send_command $sk, "part", $track, "display", "$i";
+ $info->{$i} = $f[0];
+ }
+}
+
+sub fmt_duration ($) {
+ my ($n) = @_;
+ return sprintf "%d:%02d", int $n/60, $n%60;
+}
+
+sub get_now_playing ($) {
+ my ($sk) = @_;
+ my $r = send_command $sk, "playing";
+ defined $r or return {};
+ my %info = split_fields $r;
+ decode_track_name $sk, %info;
+ exists $info{sofar} and
+ $info{length} = send_command $sk, "length", $info{track};
+ return \%info;
+}
+
+sub format_now_playing (;\%) {
+ my ($info) = @_;
+ unless (defined $info) {
+ my $sk = configured_connection;
+ $info = get_now_playing $sk;
+ close $sk;
+ }
+ exists $info->{track} or return "Nothing.";
+ my $r = "$info->{artist}: ‘$info->{title}’";
+ $r .= ", from ‘$info->{album}’" if $info->{album};
+ exists $info->{sofar} && exists $info->{length} and
+ $r .= sprintf " (%s/%s)",
+ fmt_duration $info->{sofar}, fmt_duration $info->{length};
+ $r .= "\n(chosen by $info->{submitter})" if exists $info->{submitter};
+ return $r;