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);
47 return int(0.5 + shift);
52 return $a > $b ? $a : $b;
58 my ($n, $idx, $cur) = (0, 0, -1);
60 open (my $fh, "-|", "amused", "show", "-p");
67 $idx = ++$idx % $lines;
69 last if $cur != -1 && $n - $cur > int($lines/2) &&
74 return ("Empty playlist.") unless @tmp;
87 sub playlist_numbers {
88 my ($cur, $tot, $found) = (0, 0, 0);
89 open (my $fh, "-|", "amused", "show", "-p");
100 my ($pos, $dur, $mode);
102 open (my $fh, "-|", "amused", "status", "-f",
103 "status,time:raw,mode:oneline");
108 $pos = s/position //r if m/^position /;
109 $dur = s/duration //r if m/^duration /;
110 $mode = $_ if m/^repeat/;
113 return ($status, $pos, $dur, $mode);
120 if ($seconds > 3600) {
121 my $hours = int($seconds / 3600);
122 $seconds -= $hours * 3600;
123 $str = sprintf("%02:", $hours);
126 my $minutes = int($seconds / 60);
127 $seconds -= $minutes * 60;
128 $str .= sprintf "%02d:%02d", $minutes, $seconds;
133 my ($str, $pstr) = @_;
134 my $width = mbswidth($str);
135 return $str if $width > $COLS;
136 my $pre = ($COLS - $width) / 2;
137 my $lpad = $pstr x $pre;
138 my $rpad = $pstr x ($COLS - $width - $pre + 1);
139 return ($lpad, $str, $rpad);
143 my ($y, $x, $cur, $max) = @_;
144 my ($pre, $c, $post) = center(" $cur / $max ", '-');
145 addstring $y, $x, "";
147 my $p = COLOR_PAIR($pair_n);
161 my ($y, $x, $pos, $dur) = @_;
163 my $pstr = showtime $pos;
164 my $dstr = showtime $dur;
166 my $len = $COLS - length($pstr) - length($dstr) - 4;
167 return if $len <= 0 or $dur <= 0;
168 my $filled = round($pos * $len / $dur);
170 addstring $y, $x, "$pstr [";
171 addstring "#" x $filled;
172 addstring " " x max($len - $filled, 0);
177 my ($y, $x, $status) = @_;
178 my ($pre, $c, $post) = center($status, ' ');
179 addstring $y, $x, $pre;
185 my ($y, $x, $mode) = @_;
186 my ($pre, $c, $post) = center($mode, ' ');
187 addstring $y, $x, $pre;
194 if ($LINES < 4 || $COLS < 20) {
195 addstring "window too small";
202 $longest = max $longest, length($_) foreach @songs;
203 if ($longest < $COLS) {
204 $song_pad = " " x (($COLS - $longest)/2);
209 attron(A_BOLD) if m/^>/;
210 addstring $line++, 0, $song_pad . $_;
214 offsets $LINES - 4, 0, $playlist_cur, $playlist_max;
215 progress $LINES - 3, 0, $time_cur, $time_dur;
216 show_status $LINES - 2, 0, $status;
217 show_mode $LINES - 1, 0, $mode;
223 $last_lines = $LINES;
224 @songs = excerpt $LINES - 4;
228 ($playlist_cur, $playlist_max) = playlist_numbers;
232 ($status, $time_cur, $time_dur, $mode) = status;
236 return unless defined $pfile;
238 open(my $fh, ">", $pfile);
239 open(my $ph, "-|", "amused", "show", "-p");
241 print $fh $_ while (<$ph>);
247 die "monitor quit" unless defined($l);
249 getnums if $l =~ m/load|jump|next|prev/;
250 getsongs if $l =~ m/load|jump|next|prev/;
254 my ($ch, $key) = getchar;
256 if ($key == KEY_BACKSPACE) {
257 system "amused", " seek", "0";
259 } elsif (defined $ch) {
261 system "amused", "toggle";
262 } elsif ($ch eq "<" or $ch eq "p") {
263 system "amused", "prev";
264 } elsif ($ch eq ">" or $ch eq "n") {
265 system "amused", "next";
266 } elsif ($ch eq ",") {
267 system "amused", "seek", "-5";
268 } elsif ($ch eq ".") {
269 system "amused", "seek", "+5";
270 } elsif ($ch eq "S") {
271 system "amused show | sort -u | amused load";
272 } elsif ($ch eq "R") {
273 system "amused show | sort -R | amused load";
274 } elsif ($ch eq "s") {
276 } elsif ($ch eq "q") {
278 } elsif ($ch eq "\cH") {
279 system "amused", " seek", "0"
289 my $mpid = open(my $monitor, "-|", "amused", "monitor")
290 or die "can't spawn amused monitor";
295 init_pair $pair_n, 250, -1;
302 my $poll = IO::Poll->new();
303 $poll->mask(\*STDIN => POLLIN);
304 $poll->mask($monitor => POLLIN);
307 my $tbefore = clock_gettime(CLOCK_MONOTONIC);
316 my $now = clock_gettime(CLOCK_MONOTONIC);
317 my $elapsed = $now - $tbefore;
320 $time_cur += round($elapsed)
321 if $status =~ m/^playing/;
324 getstatus unless $tick++ % 8;
326 hinput if $poll->events(\*STDIN) & POLLIN;
327 hevent $monitor if $poll->events($monitor) & POLLIN;
329 getsongs if $LINES != $last_lines;
346 amused-monitor - curses interface for amused(1)
350 B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
354 amused-monitor is a simple curses interface for amused(1).
356 The following options are available:
360 =item B<-p> I<playlist>
362 Save the current playling queue to the file I<playlist> upon exit or
365 =item B<-t> I<string>
367 Trim out the given I<string> from every song in the playlist view.
371 The following key-bindings are available:
375 =item backspace or C-h
377 Seek back to the beginning of the track.
393 Seek backward by five seconds.
397 Seek forward by five seconds.
401 Randomize the playlist.
409 Save the status to the file given with the B<-p> flag.