3 # Copyright (c) 2022 Omar Polo <op@omarpolo.com>
5 # Permission to use, copy, modify, and distribute this software for any
6 # purpose with or without fee is hereby granted, provided that the above
7 # copyright notice and this permission notice appear in all copies.
9 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 use POSIX ":sys_wait_h";
23 use Text::CharWidth qw(mbswidth);
24 use IO::Poll qw(POLLIN);
25 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
26 use Getopt::Long qw(:config bundling);
48 return int(0.5 + shift);
53 return $a > $b ? $a : $b;
59 my ($n, $idx, $cur) = (0, 0, -1);
61 open (my $fh, "-|", "amused", "show", "-p");
69 $current_song = s/^> //r;
73 $idx = ++$idx % $lines;
75 last if $cur != -1 && $n - $cur > int($lines/2) &&
80 return ("Empty playlist.") unless @tmp;
93 sub playlist_numbers {
94 my ($cur, $tot, $found) = (0, 0, 0);
95 open (my $fh, "-|", "amused", "show", "-p");
106 my ($pos, $dur, $mode);
108 open (my $fh, "-|", "amused", "status", "-f",
109 "status,time:raw,mode:oneline");
111 <$fh> =~ m/([a-z]+) (.*)/;
112 my ($status, $current_song) = ($1, $2);
116 $pos = s/position //r if m/^position /;
117 $dur = s/duration //r if m/^duration /;
118 $mode = $_ if m/^repeat/;
121 return ($status, $current_song, $pos, $dur, $mode);
128 if ($seconds > 3600) {
129 my $hours = int($seconds / 3600);
130 $seconds -= $hours * 3600;
131 $str = sprintf("%02d:", $hours);
134 my $minutes = int($seconds / 60);
135 $seconds -= $minutes * 60;
136 $str .= sprintf "%02d:%02d", $minutes, $seconds;
141 my ($str, $pstr) = @_;
142 my $width = mbswidth($str);
143 return $str if $width > $COLS;
144 my $pre = round(($COLS - $width) / 2);
145 my $lpad = $pstr x $pre;
146 my $rpad = $pstr x ($COLS - $width - $pre);
147 return ($lpad, $str, $rpad);
151 my ($y, $x, $cur, $max) = @_;
152 my ($pre, $c, $post) = center(" $cur / $max ", '-');
153 addstring $y, $x, "";
155 my $p = COLOR_PAIR($pair_n);
169 my ($y, $x, $pos, $dur) = @_;
171 my $pstr = showtime $pos;
172 my $dstr = showtime $dur;
174 my $len = $COLS - length($pstr) - length($dstr) - 4;
175 return if $len <= 0 or $dur <= 0;
176 my $filled = round($pos * $len / $dur);
178 addstring $y, $x, "$pstr [";
179 addstring "#" x $filled;
180 addstring " " x max($len - $filled, 0);
185 my ($y, $x, $status) = @_;
186 my ($pre, $c, $post) = center($status, ' ');
187 addstring $y, $x, $pre;
193 my ($y, $x, $mode) = @_;
194 my ($pre, $c, $post) = center($mode, ' ');
195 addstring $y, $x, $pre;
202 if ($LINES < 4 || $COLS < 20) {
203 addstring "window too small";
210 $longest = max $longest, length($_) foreach @songs;
211 if ($longest < $COLS) {
212 $song_pad = " " x (($COLS - $longest)/2);
217 attron(A_BOLD) if m/^>/;
218 addstring $line++, 0, $song_pad . $_;
222 offsets $LINES - 4, 0, $playlist_cur, $playlist_max;
223 progress $LINES - 3, 0, $time_cur, $time_dur;
224 show_status $LINES - 2, 0, "$status $current_song";
225 show_mode $LINES - 1, 0, $mode;
231 $last_lines = $LINES;
232 @songs = excerpt $LINES - 4;
236 ($playlist_cur, $playlist_max) = playlist_numbers;
240 return unless defined $pfile;
242 open(my $fh, ">", $pfile);
243 open(my $ph, "-|", "amused", "show", "-p");
245 print $fh $_ while (<$ph>);
251 die "monitor quit" unless defined($l);
253 $status = "playing" if $l =~ m/^play/;
254 $status = "paused" if $l =~ m/^pause/;
255 $status = "stopped" if $l =~ m/^stop/;
257 ($time_cur, $time_dur) = ($1, $2) if $l =~ m/^seek (\d+) (\d+)/;
259 $mode = $1 if $l =~ m/^mode (.*)/;
261 getnums if $l =~ m/load|jump|next|prev/;
262 getsongs if $l =~ m/load|jump|next|prev/;
266 my ($ch, $key) = getchar;
268 if ($key == KEY_BACKSPACE) {
269 system "amused", "seek", "0";
271 } elsif (defined $ch) {
273 system "amused", "toggle";
274 } elsif ($ch eq "<" or $ch eq "p") {
275 system "amused", "prev";
276 } elsif ($ch eq ">" or $ch eq "n") {
277 system "amused", "next";
278 } elsif ($ch eq ",") {
279 system "amused", "seek", "-5";
280 } elsif ($ch eq ".") {
281 system "amused", "seek", "+5";
282 } elsif ($ch eq "S") {
283 system "amused show | sort -u | amused load";
284 } elsif ($ch eq "R") {
285 system "amused show | sort -R | amused load";
286 } elsif ($ch eq "s") {
288 } elsif ($ch eq "q") {
290 } elsif ($ch eq "\cH") {
291 system "amused", "seek", "0"
301 my $mpid = open(my $monitor, "-|", "amused", "monitor")
302 or die "can't spawn amused monitor";
307 init_pair $pair_n, 250, -1;
314 my $poll = IO::Poll->new();
315 $poll->mask(\*STDIN => POLLIN);
316 $poll->mask($monitor => POLLIN);
318 if (`uname` =~ "OpenBSD") {
322 my $prog = `which amused`;
325 unveil($prog, 'rx') or die "unveil $prog: $!";
326 if (defined($pfile)) {
327 unveil($pfile, 'wc') or die "unveil $pfile: $!";
328 pledge qw(stdio wpath cpath tty proc exec) or die "pledge: $!";
330 pledge qw(stdio tty proc exec) or die "pledge: $!";
336 ($status, $current_song, $time_cur, $time_dur, $mode) = status;
341 hinput if $poll->events(\*STDIN) & POLLIN;
342 hevent $monitor if $poll->events($monitor) & POLLIN;
344 getsongs if $LINES != $last_lines;
361 amused-monitor - curses interface for amused(1)
365 B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
369 amused-monitor is a simple curses interface for amused(1).
371 The following options are available:
375 =item B<-p> I<playlist>
377 Save the current playling queue to the file I<playlist> upon exit or
380 =item B<-t> I<string>
382 Trim out the given I<string> from every song in the playlist view.
386 The following key-bindings are available:
390 =item backspace or C-h
392 Seek back to the beginning of the track.
408 Seek backward by five seconds.
412 Seek forward by five seconds.
416 Randomize the playlist.
424 Save the status to the file given with the B<-p> flag.