commit 6f8bcff
Emilia Smólska
·
2026-08-03 11:55:22 +0000 UTC
parent 180a37c
utils
4 files changed,
+455,
-1
+440,
-0
1@@ -0,0 +1,440 @@
2+#!/usr/bin/env perl
3+#
4+# Copyright (c) 2022 Omar Polo <op@omarpolo.com>
5+#
6+# Permission to use, copy, modify, and distribute this software for any
7+# purpose with or without fee is hereby granted, provided that the above
8+# copyright notice and this permission notice appear in all copies.
9+#
10+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17+
18+use strict;
19+use warnings;
20+use v5.12;
21+
22+use open ":std", ":encoding(UTF-8)";
23+use utf8;
24+
25+use Curses;
26+use POSIX qw(:sys_wait_h setlocale LC_ALL);
27+use Text::CharWidth qw(mbswidth);
28+use IO::Poll qw(POLLIN);
29+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
30+use Getopt::Long qw(:config bundling);
31+use Pod::Usage;
32+
33+my $run = 1;
34+
35+my $pfile;
36+my $trim = "";
37+
38+my $pair_n = 1;
39+
40+my @songs;
41+my $current_song;
42+my $playlist_cur;
43+my $playlist_max;
44+my $time_cur;
45+my $time_dur;
46+my $status;
47+my $mode;
48+
49+my $last_lines;
50+
51+sub round {
52+ return int(0.5 + shift);
53+}
54+
55+sub max {
56+ my ($a, $b) = @_;
57+ return $a > $b ? $a : $b;
58+}
59+
60+sub excerpt {
61+ my $lines = shift;
62+ my @tmp;
63+ my ($n, $idx, $cur) = (0, 0, -1);
64+
65+ open (my $fh, "-|", "amused", "show", "-p");
66+ while (<$fh>) {
67+ chomp;
68+ s,$trim,,;
69+ $tmp[$idx] = $_;
70+
71+ if (m/^>/) {
72+ $cur = $n;
73+ $current_song = s/^> //r;
74+ }
75+
76+ $n++;
77+ $idx = ++$idx % $lines;
78+
79+ last if $cur != -1 && $n - $cur > int($lines/2) &&
80+ $#tmp == $lines-1;
81+ }
82+ close($fh);
83+
84+ return ("Empty playlist.") unless @tmp;
85+
86+ # reorder the entries
87+ my @r;
88+ my $len = $#tmp + 1;
89+ $idx = $idx % $len;
90+ for (1..$len) {
91+ push @r, $tmp[$idx];
92+ $idx = ++$idx % $len;
93+ }
94+ return @r;
95+}
96+
97+sub playlist_numbers {
98+ my ($cur, $tot, $found) = (0, 0, 0);
99+ open (my $fh, "-|", "amused", "show", "-p");
100+ while (<$fh>) {
101+ $tot++;
102+ $cur++ unless $found;
103+ $found = 1 if m/^>/;
104+ }
105+ close($fh);
106+ return ($cur, $tot);
107+}
108+
109+sub status {
110+ my ($pos, $dur, $mode);
111+
112+ open (my $fh, "-|", "amused", "status", "-f",
113+ "status,time:raw,mode:oneline");
114+
115+ <$fh> =~ m/([a-z]+) (.*)/;
116+ my ($status, $current_song) = ($1, $2);
117+
118+ while (<$fh>) {
119+ chomp;
120+ $pos = s/position //r if m/^position /;
121+ $dur = s/duration //r if m/^duration /;
122+ $mode = $_ if m/^repeat/;
123+ }
124+ close($fh);
125+ return ($status, $current_song, $pos, $dur, $mode);
126+}
127+
128+sub showtime {
129+ my $seconds = shift;
130+ my $str = "";
131+
132+ if ($seconds > 3600) {
133+ my $hours = int($seconds / 3600);
134+ $seconds -= $hours * 3600;
135+ $str = sprintf("%02d:", $hours);
136+ }
137+
138+ my $minutes = int($seconds / 60);
139+ $seconds -= $minutes * 60;
140+ $str .= sprintf "%02d:%02d", $minutes, $seconds;
141+ return $str;
142+}
143+
144+sub center {
145+ my ($str, $pstr) = @_;
146+ my $width = mbswidth($str);
147+ return $str if $width > $COLS;
148+ my $pre = round(($COLS - $width) / 2);
149+ my $lpad = $pstr x $pre;
150+ my $rpad = $pstr x ($COLS - $width - $pre);
151+ return ($lpad, $str, $rpad);
152+}
153+
154+sub offsets {
155+ my ($y, $x, $cur, $max) = @_;
156+ my ($pre, $c, $post) = center(" $cur / $max ", '-');
157+ addstring $y, $x, "";
158+
159+ my $p = COLOR_PAIR($pair_n);
160+
161+ attron $p;
162+ addstring $pre;
163+ attroff $p;
164+
165+ addstring $c;
166+
167+ attron $p;
168+ addstring $post;
169+ attroff $p;
170+}
171+
172+sub progress {
173+ my ($y, $x, $pos, $dur) = @_;
174+
175+ my $pstr = showtime $pos;
176+ my $dstr = showtime $dur;
177+
178+ my $len = $COLS - length($pstr) - length($dstr) - 4;
179+ return if $len <= 0 or $dur <= 0;
180+ my $filled = round($pos * $len / $dur);
181+
182+ addstring $y, $x, "$pstr [";
183+ addstring "#" x $filled;
184+ addstring " " x max($len - $filled, 0);
185+ addstring "] $dstr";
186+}
187+
188+sub show_status {
189+ my ($y, $x, $status) = @_;
190+ my ($pre, $c, $post) = center($status, ' ');
191+ addstring $y, $x, $pre;
192+ addstring $c;
193+ addstring $post;
194+}
195+
196+sub show_mode {
197+ my ($y, $x, $mode) = @_;
198+ my ($pre, $c, $post) = center($mode, ' ');
199+ addstring $y, $x, $pre;
200+ addstring $c;
201+ addstring $post;
202+}
203+
204+sub render {
205+ erase;
206+ if ($LINES < 4 || $COLS < 20) {
207+ addstring "window too small";
208+ refresh;
209+ return;
210+ }
211+
212+ my $song_pad = "";
213+ my $longest = 0;
214+ $longest = max $longest, length($_) foreach @songs;
215+ if ($longest < $COLS) {
216+ $song_pad = " " x (($COLS - $longest)/2);
217+ }
218+
219+ my $line = 0;
220+ map {
221+ attron(A_BOLD) if m/^>/;
222+ addstring $line++, 0, $song_pad . $_;
223+ standend;
224+ } @songs;
225+
226+ offsets $LINES - 4, 0, $playlist_cur, $playlist_max;
227+ progress $LINES - 3, 0, $time_cur, $time_dur;
228+ show_status $LINES - 2, 0, "$status $current_song";
229+ show_mode $LINES - 1, 0, $mode;
230+
231+ refresh;
232+}
233+
234+sub getsongs {
235+ $last_lines = $LINES;
236+ @songs = excerpt $LINES - 4;
237+}
238+
239+sub getnums {
240+ ($playlist_cur, $playlist_max) = playlist_numbers;
241+}
242+
243+sub save {
244+ return unless defined $pfile;
245+
246+ open(my $fh, ">", $pfile);
247+ open(my $ph, "-|", "amused", "show", "-p");
248+
249+ print $fh $_ while (<$ph>);
250+}
251+
252+sub hevent {
253+ my $fh = shift;
254+ my $l = <$fh>;
255+ die "monitor quit" unless defined($l);
256+
257+ $status = "playing" if $l =~ m/^play/;
258+ $status = "paused" if $l =~ m/^pause/;
259+ $status = "stopped" if $l =~ m/^stop/;
260+
261+ ($time_cur, $time_dur) = ($1, $2) if $l =~ m/^seek (\d+) (\d+)/;
262+
263+ $mode = $1 if $l =~ m/^mode (.*)/;
264+
265+ getnums if $l =~ m/load|jump|next|previous/;
266+ getsongs if $l =~ m/load|jump|next|previous/;
267+}
268+
269+sub hinput {
270+ my ($ch, $key) = getchar;
271+ if (defined $key) {
272+ if ($key == KEY_BACKSPACE) {
273+ system "amused", "seek", "0";
274+ }
275+ } elsif (defined $ch) {
276+ if ($ch eq " ") {
277+ system "amused", "toggle";
278+ } elsif ($ch eq "<" or $ch eq "p") {
279+ system "amused", "previous";
280+ } elsif ($ch eq ">" or $ch eq "n") {
281+ system "amused", "next";
282+ } elsif ($ch eq ",") {
283+ system "amused", "seek", "-5";
284+ } elsif ($ch eq ".") {
285+ system "amused", "seek", "+5";
286+ } elsif ($ch eq "S") {
287+ system "amused show | sort -u | amused load";
288+ } elsif ($ch eq "R") {
289+ system "amused show | sort -R | amused load";
290+ } elsif ($ch eq "s") {
291+ save;
292+ } elsif ($ch eq "q") {
293+ $run = 0;
294+ } elsif ($ch eq "\cH") {
295+ system "amused", "seek", "0"
296+ }
297+ }
298+}
299+
300+GetOptions(
301+ "p:s" => \$pfile,
302+ "t:s" => \$trim,
303+ ) or pod2usage(1);
304+
305+my $mpid = open(my $monitor, "-|", "amused", "monitor")
306+ or die "can't spawn amused monitor";
307+
308+setlocale(LC_ALL, "");
309+initscr;
310+start_color;
311+use_default_colors;
312+init_pair $pair_n, 250, -1;
313+
314+timeout 1000;
315+scrollok 0;
316+curs_set 0;
317+keypad 1;
318+
319+my $poll = IO::Poll->new();
320+$poll->mask(\*STDIN => POLLIN);
321+$poll->mask($monitor => POLLIN);
322+
323+if (`uname` =~ "OpenBSD") {
324+ use OpenBSD::Pledge;
325+ use OpenBSD::Unveil;
326+
327+ my $prog = `which amused`;
328+ chomp $prog;
329+
330+ unveil($prog, 'rx') or die "unveil $prog: $!";
331+ if (defined($pfile)) {
332+ unveil($pfile, 'wc') or die "unveil $pfile: $!";
333+ pledge qw(stdio wpath cpath tty proc exec) or die "pledge: $!";
334+ } else {
335+ pledge qw(stdio tty proc exec) or die "pledge: $!";
336+ }
337+}
338+
339+getsongs;
340+getnums;
341+($status, $current_song, $time_cur, $time_dur, $mode) = status;
342+render;
343+
344+while ($run) {
345+ $poll->poll();
346+ hinput if $poll->events(\*STDIN) & POLLIN;
347+ hevent $monitor if $poll->events($monitor) & POLLIN;
348+
349+ getsongs if $LINES != $last_lines;
350+
351+ render;
352+}
353+
354+endwin;
355+save;
356+
357+kill 'INT', $mpid;
358+wait;
359+
360+__END__
361+
362+=pod
363+
364+=head1 NAME
365+
366+amused-monitor - curses interface for amused(1)
367+
368+=head1 SYNOPSIS
369+
370+B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
371+
372+=head1 DESCRIPTION
373+
374+amused-monitor is a simple curses interface for amused(1).
375+
376+The following options are available:
377+
378+=over 12
379+
380+=item B<-p> I<playlist>
381+
382+Save the current playling queue to the file I<playlist> upon exit or
383+I<s> key.
384+
385+=item B<-t> I<string>
386+
387+Trim out the given I<string> from every song in the playlist view.
388+
389+=back
390+
391+The following key-bindings are available:
392+
393+=over 8
394+
395+=item backspace or C-h
396+
397+Seek back to the beginning of the track.
398+
399+=item space
400+
401+Toggle play/pause.
402+
403+=item < or p
404+
405+Play previous song.
406+
407+=item > or n
408+
409+Play next song.
410+
411+=item ,
412+
413+Seek backward by five seconds.
414+
415+=item .
416+
417+Seek forward by five seconds.
418+
419+=item R
420+
421+Randomize the playlist.
422+
423+=item S
424+
425+Sort the playlist.
426+
427+=item s
428+
429+Save the status to the file given with the B<-p> flag.
430+
431+=item q
432+
433+Quit.
434+
435+=back
436+
437+=head1 SEE ALSO
438+
439+amused(1)
440+
441+=cut
+5,
-0
1@@ -0,0 +1,5 @@
2+case $1 in
3+on) verily=verily; mode='=0';;
4+off) verily=verily; mode='=1';;
5+esac
6+$verily sysctl kern.audio.record$mode kern.video.record$mode
+7,
-0
1@@ -0,0 +1,7 @@
2+#!/usr/bin/env perl
3+use strict;
4+use warnings;
5+use List::Util qw(shuffle);
6+
7+my @lines = shuffle <>;
8+print @lines;
+3,
-1
1@@ -2,11 +2,13 @@ set -xe
2 c() { ln -sf $1 $2; }
3 c $PWD/Xdefaults ~/.Xdefaults
4 mkdir -p ~/bin
5+c $PWD/bin/amused-monitor ~/bin/amused-monitor
6 c $PWD/bin/au ~/bin/au
7 c $PWD/bin/gcc ~/bin/gcc
8 c $PWD/bin/lwjgl ~/bin/lwjgl
9 c $PWD/bin/makeinfo ~/bin/makeinfo
10-c $PWD/bin/playing ~/bin/playing
11+c $PWD/bin/opsec ~/bin/opsec
12+c $PWD/bin/shuffle ~/bin/shuffle
13 c $PWD/bin/x0 ~/bin/x0
14 mkdir -p ~/.config/fontconfig
15 c $PWD/config/fontconfig/fonts.conf ~/.config/fontconfig/fonts.conf