From: Mark Wooding Date: Sat, 30 May 2020 12:16:10 +0000 (+0100) Subject: bin/disorder-notify: Rewrite and take over the functionality of `media-keys'. X-Git-Url: https://git.distorted.org.uk/~mdw/profile/commitdiff_plain/a1b30762426cce8f26c8314187e194811488aaac bin/disorder-notify: Rewrite and take over the functionality of `media-keys'. --- diff --git a/Makefile b/Makefile index 95d0a45..0e89741 100644 --- 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 +SCRIPTLINKS += disorder-notify ## 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 += media-keys -SCRIPTLINKS += disorder-notify SCRIPTLINKS += xduplic-terminal DOTCPP += .Xdefaults diff --git a/bin/disorder-notify b/bin/disorder-notify index 84c702f..e67668f 100755 --- a/bin/disorder-notify +++ b/bin/disorder-notify @@ -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) = @_; @@ -6,106 +43,252 @@ sub notify ($$) { $body =~ s:\&:&:g; $body =~ s:\<:<:g; $body =~ s:\>:>: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) // ""); + 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 index 436828d..0000000 --- a/bin/media-keys +++ /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 "$@" diff --git a/dot/e16-bindings b/dot/e16-bindings index 1b4c4eb..c1ae372 100644 --- a/dot/e16-bindings +++ b/dot/e16-bindings @@ -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 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:&:&:g; s:<:<:g; s:>:>: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