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.
21 use open ":std", ":encoding(UTF-8)";
25 use POSIX qw(:sys_wait_h setlocale LC_ALL);
26 use Text::CharWidth qw(mbswidth);
27 use IO::Poll qw(POLLIN);
28 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
29 use Getopt::Long qw(:config bundling);
51 return int(0.5 + shift);
56 return $a > $b ? $a : $b;
62 my ($n, $idx, $cur) = (0, 0, -1);
64 open (my $fh, "-|", "amused", "show", "-p");
72 $current_song = s/^> //r;
76 $idx = ++$idx % $lines;
78 last if $cur != -1 && $n - $cur > int($lines/2) &&
83 return ("Empty playlist.") unless @tmp;
96 sub playlist_numbers {
97 my ($cur, $tot, $found) = (0, 0, 0);
98 open (my $fh, "-|", "amused", "show", "-p");
101 $cur++ unless $found;
109 my ($pos, $dur, $mode);
111 open (my $fh, "-|", "amused", "status", "-f",
112 "status,time:raw,mode:oneline");
114 <$fh> =~ m/([a-z]+) (.*)/;
115 my ($status, $current_song) = ($1, $2);
119 $pos = s/position //r if m/^position /;
120 $dur = s/duration //r if m/^duration /;
121 $mode = $_ if m/^repeat/;
124 return ($status, $current_song, $pos, $dur, $mode);
131 if ($seconds > 3600) {
132 my $hours = int($seconds / 3600);
133 $seconds -= $hours * 3600;
134 $str = sprintf("%02d:", $hours);
137 my $minutes = int($seconds / 60);
138 $seconds -= $minutes * 60;
139 $str .= sprintf "%02d:%02d", $minutes, $seconds;
144 my ($str, $pstr) = @_;
145 my $width = mbswidth($str);
146 return $str if $width > $COLS;
147 my $pre = round(($COLS - $width) / 2);
148 my $lpad = $pstr x $pre;
149 my $rpad = $pstr x ($COLS - $width - $pre);
150 return ($lpad, $str, $rpad);
154 my ($y, $x, $cur, $max) = @_;
155 my ($pre, $c, $post) = center(" $cur / $max ", '-');
156 addstring $y, $x, "";
158 my $p = COLOR_PAIR($pair_n);
172 my ($y, $x, $pos, $dur) = @_;
174 my $pstr = showtime $pos;
175 my $dstr = showtime $dur;
177 my $len = $COLS - length($pstr) - length($dstr) - 4;
178 return if $len <= 0 or $dur <= 0;
179 my $filled = round($pos * $len / $dur);
181 addstring $y, $x, "$pstr [";
182 addstring "#" x $filled;
183 addstring " " x max($len - $filled, 0);
188 my ($y, $x, $status) = @_;
189 my ($pre, $c, $post) = center($status, ' ');
190 addstring $y, $x, $pre;
196 my ($y, $x, $mode) = @_;
197 my ($pre, $c, $post) = center($mode, ' ');
198 addstring $y, $x, $pre;
205 if ($LINES < 4 || $COLS < 20) {
206 addstring "window too small";
213 $longest = max $longest, length($_) foreach @songs;
214 if ($longest < $COLS) {
215 $song_pad = " " x (($COLS - $longest)/2);
220 attron(A_BOLD) if m/^>/;
221 addstring $line++, 0, $song_pad . $_;
225 offsets $LINES - 4, 0, $playlist_cur, $playlist_max;
226 progress $LINES - 3, 0, $time_cur, $time_dur;
227 show_status $LINES - 2, 0, "$status $current_song";
228 show_mode $LINES - 1, 0, $mode;
234 $last_lines = $LINES;
235 @songs = excerpt $LINES - 4;
239 ($playlist_cur, $playlist_max) = playlist_numbers;
243 return unless defined $pfile;
245 open(my $fh, ">", $pfile);
246 open(my $ph, "-|", "amused", "show", "-p");
248 print $fh $_ while (<$ph>);
254 die "monitor quit" unless defined($l);
256 $status = "playing" if $l =~ m/^play/;
257 $status = "paused" if $l =~ m/^pause/;
258 $status = "stopped" if $l =~ m/^stop/;
260 ($time_cur, $time_dur) = ($1, $2) if $l =~ m/^seek (\d+) (\d+)/;
262 $mode = $1 if $l =~ m/^mode (.*)/;
264 getnums if $l =~ m/load|jump|next|prev/;
265 getsongs if $l =~ m/load|jump|next|prev/;
269 my ($ch, $key) = getchar;
271 if ($key == KEY_BACKSPACE) {
272 system "amused", "seek", "0";
274 } elsif (defined $ch) {
276 system "amused", "toggle";
277 } elsif ($ch eq "<" or $ch eq "p") {
278 system "amused", "prev";
279 } elsif ($ch eq ">" or $ch eq "n") {
280 system "amused", "next";
281 } elsif ($ch eq ",") {
282 system "amused", "seek", "-5";
283 } elsif ($ch eq ".") {
284 system "amused", "seek", "+5";
285 } elsif ($ch eq "S") {
286 system "amused show | sort -u | amused load";
287 } elsif ($ch eq "R") {
288 system "amused show | sort -R | amused load";
289 } elsif ($ch eq "s") {
291 } elsif ($ch eq "q") {
293 } elsif ($ch eq "\cH") {
294 system "amused", "seek", "0"
304 my $mpid = open(my $monitor, "-|", "amused", "monitor")
305 or die "can't spawn amused monitor";
307 setlocale(LC_ALL, "");
311 init_pair $pair_n, 250, -1;
318 my $poll = IO::Poll->new();
319 $poll->mask(\*STDIN => POLLIN);
320 $poll->mask($monitor => POLLIN);
322 if (`uname` =~ "OpenBSD") {
326 my $prog = `which amused`;
329 unveil($prog, 'rx') or die "unveil $prog: $!";
330 if (defined($pfile)) {
331 unveil($pfile, 'wc') or die "unveil $pfile: $!";
332 pledge qw(stdio wpath cpath tty proc exec) or die "pledge: $!";
334 pledge qw(stdio tty proc exec) or die "pledge: $!";
340 ($status, $current_song, $time_cur, $time_dur, $mode) = status;
345 hinput if $poll->events(\*STDIN) & POLLIN;
346 hevent $monitor if $poll->events($monitor) & POLLIN;
348 getsongs if $LINES != $last_lines;
365 amused-monitor - curses interface for amused(1)
369 B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
373 amused-monitor is a simple curses interface for amused(1).
375 The following options are available:
379 =item B<-p> I<playlist>
381 Save the current playling queue to the file I<playlist> upon exit or
384 =item B<-t> I<string>
386 Trim out the given I<string> from every song in the playlist view.
390 The following key-bindings are available:
394 =item backspace or C-h
396 Seek back to the beginning of the track.
412 Seek backward by five seconds.
416 Seek forward by five seconds.
420 Randomize the playlist.
428 Save the status to the file given with the B<-p> flag.