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("%02d:", $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 = round(($COLS - $width) / 2);
137 my $lpad = $pstr x $pre;
138 my $rpad = $pstr x ($COLS - $width - $pre);
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);
306 if (`uname` =~ "OpenBSD") {
310 my $prog = `which amused`;
313 unveil($prog, 'rx') or die "unveil $prog: $!";
314 if (defined($pfile)) {
315 unveil($pfile, 'wc') or die "unveil $pfile: $!";
316 pledge qw(stdio wpath cpath tty proc exec) or die "pledge: $!";
318 pledge qw(stdio tty proc exec) or die "pledge: $!";
328 my $tbefore = clock_gettime(CLOCK_MONOTONIC);
332 my $now = clock_gettime(CLOCK_MONOTONIC);
333 my $elapsed = $now - $tbefore;
336 $time_cur += round($elapsed)
337 if $status =~ m/^playing/;
340 getstatus unless $tick++ % 8;
342 hinput if $poll->events(\*STDIN) & POLLIN;
343 hevent $monitor if $poll->events($monitor) & POLLIN;
345 getsongs if $LINES != $last_lines;
362 amused-monitor - curses interface for amused(1)
366 B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
370 amused-monitor is a simple curses interface for amused(1).
372 The following options are available:
376 =item B<-p> I<playlist>
378 Save the current playling queue to the file I<playlist> upon exit or
381 =item B<-t> I<string>
383 Trim out the given I<string> from every song in the playlist view.
387 The following key-bindings are available:
391 =item backspace or C-h
393 Seek back to the beginning of the track.
409 Seek backward by five seconds.
413 Seek forward by five seconds.
417 Randomize the playlist.
425 Save the status to the file given with the B<-p> flag.