bin/disorder-notify: Rewrite and take over the functionality of `media-keys'.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 30 May 2020 12:16:10 +0000 (13:16 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 30 May 2020 12:21:33 +0000 (13:21 +0100)
Makefile
bin/disorder-notify
bin/media-keys [deleted file]
dot/e16-bindings

index 95d0a45..0e89741 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -224,6 +224,7 @@ MISCLINKS           += lib/perl/DisOrder.pm
 lib/perl/DisOrder.pm_SRC = pl/DisOrder.pm
 SCRIPTLINKS            += disorder-switch-config
 SCRIPTLINKS            += disorder-propagate-autoplay
 lib/perl/DisOrder.pm_SRC = pl/DisOrder.pm
 SCRIPTLINKS            += disorder-switch-config
 SCRIPTLINKS            += disorder-propagate-autoplay
+SCRIPTLINKS            += disorder-notify
 
 ## Random scripts.
 SCRIPTLINKS            += mdw-editor mdw-pager
 
 ## Random scripts.
 SCRIPTLINKS            += mdw-editor mdw-pager
@@ -310,8 +311,6 @@ SCRIPTLINKS         += xinitcmd lock-screen xshutdown
 SCRIPTLINKS            += un-backslashify-selection
 SCRIPTLINKS            += xpra-start-xdummy
 SCRIPTLINKS            += play-rawk
 SCRIPTLINKS            += un-backslashify-selection
 SCRIPTLINKS            += xpra-start-xdummy
 SCRIPTLINKS            += play-rawk
-SCRIPTLINKS            += media-keys
-SCRIPTLINKS            += disorder-notify
 SCRIPTLINKS            += xduplic-terminal
 
 DOTCPP                 += .Xdefaults
 SCRIPTLINKS            += xduplic-terminal
 
 DOTCPP                 += .Xdefaults
index 84c702f..e67668f 100755 (executable)
@@ -1,4 +1,41 @@
-#! /usr/bin/perl
+#! /usr/bin/perl -w
+
+use autodie qw{:all};
+use strict;
+
+use DisOrder;
+use File::FcntlLock;
+use POSIX qw{:errno_h :fcntl_h};
+
+###--------------------------------------------------------------------------
+### Configuration.
+
+my %C = (config => "$ENV{HOME}/.disorder/passwd",
+        lockdir => "$ENV{HOME}/.disorder/",
+        mixer => "Master,0");
+
+my $TITLE = "DisOrder";
+my $VARIANT = "default";
+if (-l $C{config} && (my $t = readlink $C{config}) =~ /^passwd\.(.*)$/)
+  { $VARIANT = $1; $TITLE .= " ($1)"; }
+
+###--------------------------------------------------------------------------
+### Random utilities.
+
+sub run_discard_output (@) {
+  my $kid = fork();
+  if (!$kid) {
+    open STDOUT, ">/dev/null" or die "open /dev/null: $!";
+    exec @_;
+  }
+  waitpid $kid, 0;
+  if ($?) {
+    my $st;
+    if ($? >= 256) { $st = sprintf "rc = %d", $? >> 8; }
+    else { $st = sprintf "signal %d", $?; }
+    die "$_[0] failed ($st)";
+  }
+}
 
 sub notify ($$) {
   my ($head, $body) = @_;
 
 sub notify ($$) {
   my ($head, $body) = @_;
@@ -6,106 +43,252 @@ sub notify ($$) {
   $body =~ s:\&:&amp;:g;
   $body =~ s:\<:&lt;:g;
   $body =~ s:\>:&gt;:g;
   $body =~ s:\&:&amp;:g;
   $body =~ s:\<:&lt;:g;
   $body =~ s:\>:&gt;:g;
-  my $kid = fork;
-  defined $kid or return;
-  if (!$kid) {
-    open STDOUT, ">", "/dev/null";
-    exec "notify-send",
-      "-c", "DisOrder", "-i", "audio-volume-high", "-t", "5000",
-      $head, $body;
+
+  ##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 $@;
   }
   }
-  waitpid $kid, 0;
+
+  ## 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 cmd (@) {
-  my @args = @_;
-  open my $f, "-|", "disorder", @args;
-  chomp (my @r = <$f>);
-  close $f;
-  if (wantarray) { return @r; }
-  elsif (@r == 1) { return $r[0]; }
-  else { return "??? multiple lines"; }
+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;
 }
 
 }
 
-sub now_playing (;$) {
-  my ($track) = @_;
-  if (!defined $track) {
-    my @r = cmd "playing";
-    if ($r[0] =~ /^track\s+(.*)$/) { $track = $1; }
-    else { return; }
+###--------------------------------------------------------------------------
+### 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; }
   }
   }
-  my %p;
-  for my $p ("artist", "album", "title")
-    { $p{$p} = cmd "part", $track, "display", $p; }
-  if ($p{artist} =~ /^[A-Z]$/)
-    { $p{artist} = $p{album}; $p{album} = undef; }
-  elsif ($p{artist} eq "share" && $p{album} eq "disorder")
-    { next LINE; }
-  my $r = "$p{artist}: ‘$p{title}’";
-  if (defined $p{album}) { $r .= ", from ‘$p{album}’"; }
-  notify "DisOrder: now playing", $r;
+  return \%st;
 }
 
 }
 
-for (;;) {
-  open my $log, "-|", "disorder", "log";
-  my $startp = 1;
-  my $stateinfo = undef;
-  LINE: while (<$log>) {
-    chomp;
-    my @f = ();
-    my $q = my $t = undef;
-    my $e = 0;
-    my $j = -1;
-    for (my $i = 0; $i < length $_; $i++) {
-      my $ch = substr($_, $i, 1);
-      if ($e) {
-       if ($ch eq "n") { $ch = "\n"; }
-       $t .= $ch; $e = 0;
-      } elsif ($ch eq $q) {
-       push @f, $t; $q = $t = undef;
-      } elsif (defined $q) {
-       if ($ch eq "\\") { $e = 1; }
-       else { $t .= $ch; }
-      } elsif ($ch eq " ") {
-       push @f, $t if defined $t; $t = undef;
-      } elsif (!defined $t && ($ch eq '"' || $ch eq "'")) {
-       $t //= ""; $q = $ch; $j = $i;
-      } else {
-       $t //= ""; $t .= $ch;
-      }
-    }
-    defined $q and die "unmatched $q (pos $j) in: $_";
-    push @f, $t if defined $t;
-
-    my $what = $f[1];
-    if ($what eq "volume" && $startp) {
-      $startp = 0;
-      notify "DisOrder state", "Connected: $startinfo";
-      now_playing;
-    } elsif ($what eq "state") {
-      my $st = $f[2];
-      my $msg;
-      my $np = 0;
-      if ($st eq "disable_random") { $msg = "random play disabled"; }
-      elsif ($st eq "enable_random") { $msg = "random play enabled"; }
-      elsif ($st eq "disable_play") { $msg = "playing disabled"; }
-      elsif ($st eq "enable_play") { $msg = "playing enabled"; }
-      elsif ($st eq "pause") { $msg = "paused"; }
-      elsif ($st eq "resume") { $msg = "playing"; $np = 1; }
-      else { next LINE; }
-      if (!$startp) {
-       notify "DisOrder state", ucfirst $msg;
-       now_playing if $np;
-      } else {
-       if (defined $startinfo) { $startinfo .= "; " . $msg; }
-       else { $startinfo = $msg; }
-      }
-    } elsif ($what eq "scratched") {
-      notify "DisOrder state", "Scratched playing track";
-    } elsif ($what eq "playing") {
-      now_playing $f[2];
+sub get_state () {
+  my $sk = connect_to_server $C{config};
+  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 format_now_playing (\%) {
+  my ($info) = @_;
+  exists $info->{track} or return "Nothing.";
+  my $r = "$info->{artist}: ‘$info->{title}’";
+  $r .= ", from ‘$info->{album}’" if $info->{album};
+  $r .= "\n(chosen by $info->{submitter})" if exists $info->{submitter};
+  return $r;
+}
+
+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;
+  return \%info;
+}
+
+sub watch_and_notify0 ($) {
+  my ($now_playing) = @_;
+
+  my $sk = connect_to_server $C{config}, 1;
+  my $sk_log = connect_to_server $C{config}, 1;
+
+  send_command0 $sk_log, "log";
+  my $st = get_state0 $sk_log;
+  my $msg = "playing " . ($st->{play} ? "enabled" : "disabled");
+  $msg .= "; random play " . ($st->{random} ? "enabled" : "disabled");
+  $msg .= "; " . ($st->{pause} ? "paused" : "playing");
+  notify "$TITLE state", "Connected: $msg";
+  if ($st->{play} && $now_playing) {
+    my $info = get_now_playing $sk;
+    notify "$TITLE: Now playing", format_now_playing %$info;
+  }
+
+  while (my $line = readline $sk_log) {
+    my @f = split_fields $line;
+
+    if ($f[1] eq "state") {
+      my $msg = undef;
+      if ($f[2] eq "disable_random") { $msg = "Random play disabled"; }
+      elsif ($f[2] eq "enable_random") { $msg = "Random play enabled"; }
+      elsif ($f[2] eq "disable_play") { $msg = "Playing disabled"; }
+      elsif ($f[2] eq "enable_play") { $msg = "Playing enabled"; }
+      elsif ($f[2] eq "pause") { $msg = "Paused"; }
+      elsif ($f[2] eq "resume") { $msg = "Playing"; }
+      notify "$TITLE state", $msg if defined $msg;
+    } elsif ($f[1] eq "playing") {
+      my %info;
+      $info{track} = $f[2];
+      $info{submitter} = $f[3] if @f > 3;
+      decode_track_name $sk, %info;
+      notify "$TITLE: Now playing", format_now_playing %info;
+    } elsif ($f[1] eq "scratched") {
+      my %info;
+      $info{track} = $f[2];
+      decode_track_name $sk, %info;
+      notify "$TITLE: Scratched by $f[3]", format_now_playing %info;
     }
   }
     }
   }
-  close $log;
-  sleep 5;
+
+  notify "$TITLE state", "Lost connection";
+
+  close $sk;
+  close $sk_log;
 }
 }
+
+sub watch_and_notify ($) {
+  my ($now_playing) = @_;
+
+  fork and exit 0;
+  claim_lock or exit 1;
+
+  for (;;) {
+    eval { watch_and_notify0 $now_playing; };
+    $now_playing = 1;
+    sleep 5;
+  }
+}
+
+###--------------------------------------------------------------------------
+### User-facing operations.
+
+my %OP;
+
+$OP{"volume-up"} =
+  sub { run_discard_output "amixer", "sset", $C{mixer}, "5\%+"; };
+$OP{"volume-down"} =
+  sub { run_discard_output "amixer", "sset", $C{mixer}, "5\%-"; };
+
+$OP{"scratch"} = sub {
+  my $sk = connect_to_server $C{config};
+  send_command $sk, "scratch";
+  close $sk;
+};
+
+$OP{"enable/disable"} = sub {
+  my $st = get_state;
+  my $sk = connect_to_server $C{config};
+  if ($st->{play}) { send_command $sk, "disable"; }
+  else { send_command $sk, "enable"; }
+  close $sk;
+};
+
+$OP{"play/pause"} = sub {
+  my $st = get_state;
+  my $sk = connect_to_server $C{config};
+  if (!$st->{play}) {
+    send_command $sk, "enable";
+    if ($st->{pause}) { send_command $sk, "resume"; }
+  } else {
+    if ($st->{pause}) { send_command $sk, "resume"; }
+    else { send_command $sk, "pause"; }
+  }
+  close $sk;
+};
+
+$OP{"watch"} = sub {
+  if (defined (my $lkpid = locked_by)) {
+    print STDERR "$0: already watched by pid $lkpid\n";
+    exit 2;
+  }
+  watch_and_notify 1;
+};
+
+$OP{"now-playing"} = sub {
+  my $sk = connect_to_server $C{config};
+  my $info = get_now_playing $sk;
+  close $sk;
+  print format_now_playing %$info;
+  print "\n";
+};
+
+$OP{"notify-now-playing"} = sub {
+  my $sk = connect_to_server $C{config};
+  my $info = get_now_playing $sk;
+  close $sk;
+  notify "$TITLE: Now playing", format_now_playing %$info;
+  defined locked_by or watch_and_notify 0;
+};
+
+###--------------------------------------------------------------------------
+### Main program.
+
+if (@ARGV != 1) { print STDERR "usage: $0 OP\n"; exit 2; }
+my $op = $ARGV[0];
+if (!exists $OP{$op}) { print STDERR "$0: unknown op `$op'\n"; exit 2; }
+$OP{$op}();
+
+###----- That's all, folks --------------------------------------------------
diff --git a/bin/media-keys b/bin/media-keys
deleted file mode 100755 (executable)
index 436828d..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#! /usr/bin/zsh -e
-
-bail () { echo >&2 "$*"; exit 2; }
-
-disorder=(disorder)
-
-get-disorder-state () {
-  coproc stdbuf -oL $disorder log; kid=$!
-
-  while read -Ap i; do
-    case $i[2] in
-      state)
-       case $i[3] in
-         enable_random) randp=t ;;
-         disable_random) randp=nil ;;
-         enable_play) playp=t ;;
-         disable_play) playp=nil ;;
-         resume) pausep=nil ;;
-         pause) pausep=t ;;
-       esac
-       ;;
-      *)
-       break
-       ;;
-    esac
-  done
-  kill $kid
-}
-
-alsa_mixer=Master,0
-op-volume-up () { amixer sset $alsa_mixer 5%+ >/dev/null; }
-op-volume-down () { amixer sset $alsa_mixer 5%- >/dev/null; }
-
-op-now-playing () {
-  coproc $disorder playing; kid=$!
-  read -p k track
-  case $k in
-    nothing) echo "Not playing."; return ;;
-    track) ;;
-    *) bail "unexpected first-line token \`$k'" ;;
-  esac
-  unset who
-  read -p k _
-  case $k in id) ;; *) bail "unexpected second-line token \`$k'" ;; esac
-  read -p k _ name _
-  case $k in picked) who=$name; read -p k _ ;; esac
-  case $k in played) ;; *) bail "unexpected third-line token \`$k'" ;; esac
-  read -pA t
-  case $t[-1] in started | ok) st="" ;; paused) st=" (paused)" ;; esac
-  kill $kid >/dev/null 2>&1 || :
-
-  artist=$(disorder part $track display artist)
-  album=$(disorder part $track display album)
-  title=$(disorder part $track display title)
-  case $artist in [A-Z]) artist=$album album= ;; esac
-  echo "$artist: ‘$title’${album+, from ‘$album’}${who+
-(chosen by $who)}"
-}
-
-op-scratch () { disorder scratch; }
-op-enable/disable () {
-  get-disorder-state
-  case $playp in
-    t) disorder disable ;;
-    nil) disorder enable ;;
-  esac
-}
-op-play/pause () {
-  get-disorder-state
-  case $playp,$pausep in
-    nil,t) disorder enable; disorder resume ;;
-    nil,nil) disorder enable ;;
-    t,t) disorder resume ;;
-    t,nil) disorder pause ;;
-  esac
-}
-
-if [[ -e $HOME/etc/media-keys.local ]]; then
-  . $HOME/etc/media-keys.local
-fi
-
-case $# in 0) echo >&2 "usage: $0 OP"; exit 2 ;; esac
-op=$1; shift
-case $(whence -w op-$op) in
-  "op-$op: function") ;;
-  *) echo >&2 "$0: unknown operation \`$op'"; exit 2 ;;
-esac
-op-$op "$@"
index 1b4c4eb..c1ae372 100644 (file)
@@ -59,12 +59,12 @@ KeyDown   C5        w menus show winops.menu
 KeyDown   C5        x wop * close
 KeyDown   C5 numbersign exec xinitcmd pavucontrol
 KeyDown   C5 apostrophe exec xinitcmd disobedience
 KeyDown   C5        x wop * close
 KeyDown   C5 numbersign exec xinitcmd pavucontrol
 KeyDown   C5 apostrophe exec xinitcmd disobedience
-KeyDown   C5     plus exec media-keys volume-up
-KeyDown   C5    minus exec media-keys volume-down
-KeyDown   C5   period exec media-keys enable/disable
-KeyDown   C5    comma exec notify-send -c DisOrder -i audio-volume-high -t 5000 "DisOrder: now playing" "$(media-keys now-playing | sed 's:&:&amp;:g; s:<:&lt;:g; s:>:&gt;:g')"
-KeyDown   C5    slash exec media-keys scratch
-KeyDown   C5    space exec media-keys play/pause
+KeyDown   C5     plus exec disorder-notify volume-up
+KeyDown   C5    minus exec disorder-notify volume-down
+KeyDown   C5   period exec disorder-notify enable/disable
+KeyDown   C5    comma exec disorder-notify notify-now-playing
+KeyDown   C5    slash exec disorder-notify scratch
+KeyDown   C5    space exec disorder-notify play/pause
 KeyDown   C5       F1 menus show file.menu
 KeyDown   C5       F2 menus show enlightenment.menu
 KeyDown   C5       F3 menus show settings.menu
 KeyDown   C5       F1 menus show file.menu
 KeyDown   C5       F2 menus show enlightenment.menu
 KeyDown   C5       F3 menus show settings.menu