summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Nystrom <sam@samnystrom.dev>2024-10-15 13:29:10 -0400
committerSam Nystrom <sam@samnystrom.dev>2024-10-15 13:29:10 -0400
commit2c98bbacc8db3b251e1679f9da84cf1f5ed5726a (patch)
tree10d44c9b82d275d66d880ff8ec33992cddda12b5
parentc8c79e6c6a5c4bb1e0ba44f309ce5ae612f97e1c (diff)
update ~/bin
-rwxr-xr-xbin/av1ify17
-rwxr-xr-xbin/bun2
-rwxr-xr-xbin/dwl4
-rw-r--r--bin/ff-catwalk.c50
-rwxr-xr-xbin/ffimv9
-rwxr-xr-xbin/harespec3
-rwxr-xr-xbin/hiprompt-wmenu28
-rw-r--r--bin/infinity.c216
-rwxr-xr-xbin/init51
-rwxr-xr-xbin/jq2
-rwxr-xr-xbin/pastebin5
-rwxr-xr-xbin/readmail19
-rwxr-xr-xbin/s6-rclocal-compile13
-rwxr-xr-xbin/s6-rclocal-make-logger14
-rwxr-xr-xbin/slim3
-rwxr-xr-xbin/statusline81
-rw-r--r--bin/statusline.ha319
-rwxr-xr-xbin/statusline.sh (renamed from bin/statusbar)18
-rwxr-xr-xbin/waylock2
-rwxr-xr-xbin/wmenu13
-rwxr-xr-xbin/wmenu-run2
-rwxr-xr-xbin/wmenu-yesno4
22 files changed, 864 insertions, 11 deletions
diff --git a/bin/av1ify b/bin/av1ify
new file mode 100755
index 0000000..a68ad3d
--- /dev/null
+++ b/bin/av1ify
@@ -0,0 +1,17 @@
+#!/bin/execlineb -s1
+backtick -E webm { heredoc 0 ${1} sed "s/\.[^.]*$/.webm/" }
+# nice -n 20
+time
+ffmpeg
+ -i ${1}
+ -c:v libsvtav1
+ -g 240
+ -preset 4
+ -pix_fmt yuv420p10le
+ -svtav1-params film-grain-denoise=0:film-grain=20:tune=0
+ -crf 34
+ -c:a libopus
+ -b:a 96k
+ -ac 2
+ ${@}
+ ${webm}
diff --git a/bin/bun b/bin/bun
new file mode 100755
index 0000000..0bd51dd
--- /dev/null
+++ b/bin/bun
@@ -0,0 +1,2 @@
+#!/command/execlineb -s0
+nix run --offline nixpkgs#bun -- ${@}
diff --git a/bin/dwl b/bin/dwl
new file mode 100755
index 0000000..b169367
--- /dev/null
+++ b/bin/dwl
@@ -0,0 +1,4 @@
+#!/bin/execlineb -P
+if { rm -f ${XDG_RUNTIME_DIR}/dwl-stdout }
+if { mkfifo ${XDG_RUNTIME_DIR}/dwl-stdout }
+/usr/local/bin/dwl -s "superd & cat > \"$XDG_RUNTIME_DIR\"/dwl-stdout"
diff --git a/bin/ff-catwalk.c b/bin/ff-catwalk.c
new file mode 100644
index 0000000..69b827e
--- /dev/null
+++ b/bin/ff-catwalk.c
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <endian.h>
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 5) {
+ fprintf(stderr, "Usage: %s LATTE FRAPPE MACCHIATO MOCHA\n", argv[0]);
+ return 1;
+ }
+ FILE *images[4];
+ uint32_t headers[16];
+ for (int i = 0; i < 4; i++) {
+ images[i] = fopen(argv[i+1], "r");
+ if (!images[i]) {
+ perror(argv[i+1]);
+ return 1;
+ }
+ if (fread(&headers + i*16, 16, 1, images[i]) != 1 || memcmp(&headers, &headers + i*16, 16)) {
+ fprintf(stderr, "error: header mismatch");
+ return 1;
+ }
+ }
+ uint32_t width = be32toh(headers[2]);
+ uint32_t height = be32toh(headers[3]);
+ char *buf = calloc(width, 8);
+
+ fwrite(&headers, 16, 1, stdout);
+ for (uint32_t row = 0; row < height; row++) {
+ for (int i = 0; i < 4; i++) {
+ if (fread(buf, 8, width, images[i]) != width) {
+ if (ferror(images[i])) {
+ perror("fread");
+ } else {
+ perror("fread: unexpected EOF");
+ }
+ return 1;
+ }
+ uint32_t start = width / 4 * i + width / 8 - width / 4 * row / height;
+ uint32_t end = start + width / 4;
+ if (i == 0) start = 0;
+ if (i == 3) end = width;
+ fwrite(buf + 8 * start, 8, end - start, stdout);
+ }
+ }
+ return 0;
+}
diff --git a/bin/ffimv b/bin/ffimv
new file mode 100755
index 0000000..1d2cfab
--- /dev/null
+++ b/bin/ffimv
@@ -0,0 +1,9 @@
+#!/bin/execlineb -P
+backtick -E tmp { mktemp }
+foreground {
+ if { redirfd -w 1 ${tmp} ff2png }
+ imv ${tmp}
+}
+if { rm -f ${tmp} }
+importas -ui ? ?
+exit ${?}
diff --git a/bin/harespec b/bin/harespec
new file mode 100755
index 0000000..7bfeaf9
--- /dev/null
+++ b/bin/harespec
@@ -0,0 +1,3 @@
+#!/command/execlineb -P
+importas -i HOME HOME
+zathura ${HOME}/Documents/hare-specification.pdf
diff --git a/bin/hiprompt-wmenu b/bin/hiprompt-wmenu
new file mode 100755
index 0000000..91fd50e
--- /dev/null
+++ b/bin/hiprompt-wmenu
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+read -r version
+[ ! "$version" = 'version' ] && exit 1
+printf 'version 0.0.0\n'
+
+keys=
+while read -r command; do
+ case "$command" in
+ 'key '*)
+ keys="$keys\n${command#key\ }"
+ ;;
+ 'prompt '*)
+ case "${command#prompt\ }" in
+ disclose) prompt='Disclose keys?' ;;
+ delete) prompt='Delete keys?' ;;
+ esac
+ exec test "$(printf 'Yes\nNo\n%b\n' "$keys" | wmenu -l 20 -p "$prompt")" = Yes
+ ;;
+ 'password incorrect'|'unlock')
+ prompt='Unlock keyring:'
+ [ "$command" = 'password incorrect' ] && prompt="Password incorrect. $prompt"
+ pass="$(wmenu -p "$prompt" </dev/null)"
+ [ -z "$pass" ] && exit 1
+ printf 'password %s\n' "$pass"
+ ;;
+ esac
+done
diff --git a/bin/infinity.c b/bin/infinity.c
new file mode 100644
index 0000000..10a45b0
--- /dev/null
+++ b/bin/infinity.c
@@ -0,0 +1,216 @@
+/*
+ infinity.c: the Book of Infinity, adapted for skalibs.
+ Original work by David Madore: http://www.madore.org/~david/
+ Adaptation by Laurent Bercot: http://skarnet.org/
+ License: GNU GPL v2, with the special plea that the text of the Book
+ not be modified.
+ The original code can be found at:
+ http://www.madore.org/~david/programs/programs-1.36.html#prog_infinity
+*/
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/env.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/sha256.h>
+
+static char const STRING1[] = "Wake! For the Sun, who scatter'd into flight\n" ;
+static char const STRING2[] = "The Stars before him from the Field of Night,\n" ;
+static char const STRING3[] = "\240\240\240Drives Night along with them from Heav'n, and strikes\n" ;
+static char const STRING4[] = "The Sult\341n's Turret with a Shaft of Light.\n" ;
+
+#define die() return(111)
+
+
+typedef struct magic_string_state_s magic_string_state_t, *magic_string_state_t_ref ;
+struct magic_string_state_s
+{
+ char vowel ;
+ char sequent ;
+} ;
+
+#define MAGIC_STRING_STATE_ZERO { 0, 0 } ;
+
+static char const *magic_string (unsigned char data_byte, magic_string_state_t_ref st, char final)
+{
+ char const *s ;
+ char oldsequent = st->sequent ;
+ if (((data_byte % 7) == 0 ) && st->sequent && !final)
+ {
+ st->sequent = 0 ;
+ return " " ;
+ }
+ st->sequent = 1 ;
+ if (st->vowel)
+ {
+ st->vowel = 0 ;
+ if (data_byte < 40) s = "a" ;
+ else if (data_byte < 80) s = "e" ;
+ else if (data_byte < 115) s = "i" ;
+ else if (data_byte < 145) s = "o" ;
+ else if (data_byte < 175) s = "u" ;
+ else if (data_byte < 195) s = "y" ;
+ else if (data_byte < 200) s = "ai" ;
+ else if (data_byte < 205) s = "ei" ;
+ else if (data_byte < 210) s = "oi" ;
+ else if (data_byte < 215) s = "au" ;
+ else if (data_byte < 220) s = "eu" ;
+ else if (data_byte < 225) s = "ou" ;
+ else if (data_byte < 230) s = "ae" ;
+ else if (data_byte < 235) s = "oe" ;
+ else if (data_byte < 240) s = "aa" ;
+ else if (data_byte < 250) s = "'" ;
+ else
+ {
+ s = "" ;
+ st->sequent = oldsequent ;
+ }
+ }
+ else
+ {
+ st->vowel = 1 ;
+ if ( data_byte < 12 ) s = "p" ;
+ else if (data_byte < 24) s = "b" ;
+ else if (data_byte < 36) s = "t" ;
+ else if (data_byte < 48) s = "d" ;
+ else if (data_byte < 60) s = "k" ;
+ else if (data_byte < 72) s = "g" ;
+ else if (data_byte < 84) s = "s" ;
+ else if (data_byte < 96) s = "z" ;
+ else if (data_byte < 108) s = "sh" ;
+ else if (data_byte < 120) s = "zh" ;
+ else if (data_byte < 132) s = "f" ;
+ else if (data_byte < 144) s = "v" ;
+ else if (data_byte < 156) s = "ch" ;
+ else if (data_byte < 168) s = "j" ;
+ else if (data_byte < 174) s = "ks" ;
+ else if (data_byte < 180) s = "x" ;
+ else if (data_byte < 192) { s = "h" ; st->vowel = (data_byte < 190) ; }
+ else if (data_byte < 204) { s = "n" ; st->vowel = (data_byte < 198) ; }
+ else if (data_byte < 216) { s = "r" ; st->vowel = (data_byte < 210) ; }
+ else if (data_byte < 222) s = "st" ;
+ else if (data_byte < 228) s = "zd" ;
+ else if (data_byte < 234) s = "ts" ;
+ else if (data_byte < 240) s = "dz" ;
+ else if (data_byte < 242) s = "pn" ;
+ else if (data_byte < 244) s = "bn" ;
+ else if (data_byte < 247) s = "sht" ;
+ else if (data_byte < 250) s = "zhd" ;
+ else
+ {
+ s = "" ;
+ st->sequent = oldsequent ;
+ }
+ }
+ return s ;
+}
+
+static int page_make_name (stralloc *sa, char const *seed, unsigned int seedlen)
+{
+ SHA256Schedule context = SHA256_INIT() ;
+ magic_string_state_t st = MAGIC_STRING_STATE_ZERO ;
+ char data[64] ;
+
+ sha256_update(&context, STRING1, sizeof(STRING1) - 1) ;
+ sha256_update(&context, seed, seedlen) ;
+ sha256_update(&context, STRING2, sizeof(STRING2) - 1) ;
+ sha256_update(&context, seed, seedlen) ;
+ sha256_final(&context, data) ;
+ sha256_init(&context) ;
+ sha256_update(&context, STRING3, sizeof(STRING3) - 1) ;
+ sha256_update(&context, seed, seedlen) ;
+ sha256_update(&context, STRING4, sizeof(STRING4) - 1) ;
+ sha256_update(&context, seed, seedlen) ;
+ sha256_final(&context, data + 32) ;
+
+ {
+ unsigned int i = 0 ;
+ for (; i < 64 ; i++)
+ if (!stralloc_cats(sa, magic_string(data[i], &st, i == 63)))
+ return 0 ;
+ }
+ return 1 ;
+}
+
+int main (void)
+{
+ stralloc url_start = STRALLOC_ZERO ;
+ stralloc page_name = STRALLOC_ZERO ;
+ stralloc new_path = STRALLOC_ZERO ;
+ stralloc new_page_name = STRALLOC_ZERO ;
+ char const *script_name = getenv("SCRIPT_NAME") ;
+ unsigned int new_path_start ;
+ unsigned int i = 0 ;
+
+ if (!script_name) script_name = "infinity.cgi" ;
+
+ {
+ char const *x = getenv("PATH_INFO") ;
+ if (!x || !*x) x = "/" ;
+ if (!stralloc_cats(&new_path, x)) die() ;
+ x = getenv("SERVER_NAME") ;
+ if (x)
+ {
+ if (!stralloc_cats(&url_start, "//")) die() ;
+ if (!stralloc_cats(&url_start, x)) die() ;
+ x = getenv("SERVER_PORT") ;
+ if (x && strcmp(x, "80"))
+ {
+ if (!stralloc_catb(&url_start, ":", 1)) die() ;
+ if (!stralloc_cats(&url_start, x)) die() ;
+ }
+ }
+ }
+
+ if (!page_make_name(&page_name, new_path.s, new_path.len)) die() ;
+ buffer_puts(buffer_1, "Last-Modified: Wed, 24 May 2017 12:20:00 GMT\r\nContent-Type: text/html\r\n\r\n<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\r\n<meta http-equiv=\"Content-Language\" content=\"en\" />\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n<title>") ;
+ buffer_put(buffer_1, page_name.s, page_name.len) ;
+ buffer_puts(buffer_1, "</title>\r\n<meta name=\"robots\" content=\"noindex, nofollow\" />\r\n<meta name=\"description\" content=\"One Page from the Book of Infinity\" />\r\n<meta name=\"keywords\" content=\"infinite web space ") ;
+ buffer_put(buffer_1, page_name.s, page_name.len) ;
+ buffer_puts(buffer_1, "\" />\r\n</head>\r\n<body>\r\n<h1>") ;
+ buffer_put(buffer_1, page_name.s, page_name.len) ;
+ buffer_puts(buffer_1, "</h1>\r\n<p> You are in a maze of twisty little web pages, all alike. </p>\r\n<p> From here you can get to the following places: </p>\r\n<ul>\r\n") ;
+
+ new_path_start = new_path.len ;
+ for (; i < 14 ; i++)
+ {
+ new_path.len = new_path_start ;
+ buffer_puts(buffer_1, "<li>") ;
+ switch (i)
+ {
+ case 0 :
+ case 13 :
+ {
+ while (new_path.len > 1) if (new_path.s[--new_path.len - 1] == '/') break ;
+ if (!i) buffer_puts(buffer_1, "Back to ") ;
+ else
+ {
+ if (!stralloc_catb(&new_path, page_name.s, byte_chr(page_name.s, page_name.len, ' '))) die() ;
+ if (!stralloc_catb(&new_path, "/", 1)) die() ;
+ }
+ break ;
+ }
+ default:
+ {
+ char const *const zodiac[12] = { "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpius", "sagittarius", "capricornus", "aquarius", "pisces" } ;
+ if (!stralloc_cats(&new_path, zodiac[i-1])) die() ;
+ if (!stralloc_catb(&new_path, "/", 1)) die() ;
+ }
+ }
+ buffer_puts(buffer_1, "<a href=\"") ;
+ if (url_start.s) buffer_put(buffer_1, url_start.s, url_start.len) ;
+ buffer_puts(buffer_1, script_name) ;
+ buffer_put(buffer_1, new_path.s, new_path.len) ;
+ buffer_puts(buffer_1, "\">") ;
+ new_page_name.len = 0 ;
+ if (!page_make_name(&new_page_name, new_path.s, new_path.len)) die() ;
+ buffer_put(buffer_1, new_page_name.s, new_page_name.len) ;
+ buffer_puts(buffer_1, "</a></li>\r\n") ;
+ }
+ buffer_putsflush(buffer_1, "</ul>\r\n</body>\r\n</html>\r\n") ;
+ return 0 ;
+}
+
diff --git a/bin/init b/bin/init
new file mode 100755
index 0000000..436e55a
--- /dev/null
+++ b/bin/init
@@ -0,0 +1,51 @@
+#!/bin/execlineb -P
+importas -i XDG_RUNTIME_DIR XDG_RUNTIME_DIR
+importas -i HOME HOME
+
+foreground { redirfd -w 2 /dev/null
+ foreground { find ${XDG_RUNTIME_DIR} -maxdepth 1 ! -path ${XDG_RUNTIME_DIR} -exec rm -rf "{}" + }
+ foreground { mkdir -p
+ ${HOME}/.local/state/s6/uncaught-logs
+ ${XDG_RUNTIME_DIR}/service/.s6-svscan
+ ${XDG_RUNTIME_DIR}/service/s6-svscan-log
+ }
+ ln -sf /bin/false ${XDG_RUNTIME_DIR}/service/.s6-svscan/crash
+}
+execline-cd ${XDG_RUNTIME_DIR}/service
+if {
+ redirfd -w 1 .s6-svscan/finish
+ heredoc 0 "#!/bin/execlineb -P\ns6-svc -x -- \"${XDG_RUNTIME_DIR}/service/s6-svscan-log\"\n"
+ cat
+}
+if { chmod 0755 .s6-svscan/finish }
+foreground { redirfd -w 2 /dev/null mkfifo -m 0600 s6-svscan-log/fifo }
+if { redirfd -w 1 s6-svscan-log/notification-fd echo 3 }
+if {
+ redirfd -w 1 s6-svscan-log/run
+ heredoc 0 "#!/bin/execlineb -P
+redirfd -rnb 0 fifo
+exec -c
+s6-log -bpd3 -- t ${HOME}/.local/state/s6/uncaught-logs\n"
+ cat
+}
+if { chmod 0755 s6-svscan-log/run }
+
+redirfd -r 0 /dev/null
+redirfd -wnb 1 s6-svscan-log/fifo
+fdmove -c 2 1
+
+fdreserve 2
+multisubstitute { importas -u fdr FD0 importas -u fdw FD1 }
+piperw ${fdr} ${fdw}
+background {
+ fdclose ${fdw}
+ foreground { fdmove 0 ${fdr} redirfd -w 1 /dev/null cat }
+ s6-svlisten -U { ./s6-svscan-log }
+ if { s6-rc-init -bl ${XDG_RUNTIME_DIR}/s6-rc
+ -c ${HOME}/.local/share/s6-rc/compiled
+ ${XDG_RUNTIME_DIR}/service }
+ s6-rc -l ${XDG_RUNTIME_DIR}/s6-rc -bu change default
+}
+fdclose ${fdr}
+emptyenv -c
+s6-svscan -d ${fdw} -- ${XDG_RUNTIME_DIR}/service
diff --git a/bin/jq b/bin/jq
new file mode 100755
index 0000000..fcc6d20
--- /dev/null
+++ b/bin/jq
@@ -0,0 +1,2 @@
+#!/command/execlineb -s0
+jaq ${@}
diff --git a/bin/pastebin b/bin/pastebin
index 46be94a..87d26cf 100755
--- a/bin/pastebin
+++ b/bin/pastebin
@@ -14,7 +14,7 @@ while getopts e:h opt; do
cat <<-EOF
Usage: ${0##*/} [-e EXPIRES] [FILE]
-Upload FILE (or stdin) to 0x0.st and print the URL
+Upload FILE (or stdin) to envs.sh and print the URL
-e EXPIRES Set the expiration time, either hours or epoch milliseconds
EOF
@@ -34,7 +34,6 @@ fi
file="${1:-/dev/stdin}"
[ "$file" = '-' ] && file="/dev/stdin"
-url="$(curl -fsS -F file=@"$file" ${expires:+-F expires="$expires"} https://0x0.st)"
+url="$(curl -fsS -F file=@"$file" ${expires:+-F expires="$expires"} https://envs.sh)"
printf '%s\n' "$url"
wl-copy "$url"
-xdg-open "$url"
diff --git a/bin/readmail b/bin/readmail
new file mode 100755
index 0000000..1643706
--- /dev/null
+++ b/bin/readmail
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+dir=~/mail/"${1:-INBOX}"
+
+while true; do
+ mlist "$dir" | msort -dr | mseq -S | MBLAZE_PAGER=cat mscan
+ read -r addr cmd || break
+ case "$cmd" in
+ g) mbsync primary >/dev/null 2>&1 && minc ~/mail/* >/dev/null ;;
+ c) mseq -C "$addr" ;;
+ s) mshow "$addr" && mflag -S "$addr" >/dev/null ;;
+ a) mrefile "$addr" ~/mail/Archive ;;
+ m*) mrefile "$addr" ~/mail/"${cmd#m}" ;;
+ f*) mflag -"${cmd#f}" "$addr" >/dev/null ;;
+ q) break ;;
+ esac
+done
+echo 'Syncing changes...'
+mbsync primary >/dev/null 2>&1
diff --git a/bin/s6-rclocal-compile b/bin/s6-rclocal-compile
new file mode 100755
index 0000000..ec87a7d
--- /dev/null
+++ b/bin/s6-rclocal-compile
@@ -0,0 +1,13 @@
+#!/bin/execlineb -S0
+multisubstitute {
+ importas -i HOME HOME
+ importas -i XDG_RUNTIME_DIR XDG_RUNTIME_DIR
+}
+execline-cd ${HOME}/.local/share/s6-rc
+elglob -0 old compiled-*
+backtick -E now { pipeline { echo } s6-tai64n }
+if { s6-rc-compile compiled-${now} ${HOME}/.config/s6-rc }
+foreground { s6-rc-update -l ${XDG_RUNTIME_DIR}/s6-rc ${HOME}/.local/share/s6-rc/compiled-${now} }
+if { ln -sf compiled-${now} compiled/compiled }
+if { mv -f compiled/compiled . }
+rm -rf ${old}
diff --git a/bin/s6-rclocal-make-logger b/bin/s6-rclocal-make-logger
new file mode 100755
index 0000000..dcd691b
--- /dev/null
+++ b/bin/s6-rclocal-make-logger
@@ -0,0 +1,14 @@
+#!/bin/sh
+for s in *-log; do
+ echo 3 > "$s"/notification-fd
+ echo longrun > "$s"/type
+ echo "${s%log}"pipeline > "$s"/pipeline-name
+ echo "${s%-log}" > "$s"/consumer-for
+ echo "$s" > "${s%-log}"/producer-for
+ cat <<EOF > "$s"/run
+#!/bin/execlineb -P
+exec -c
+s6-log -d3 -- T /home/samn/.local/state/s6/logs/${s%-log}
+EOF
+ chmod +x "$s"/run
+done
diff --git a/bin/slim b/bin/slim
new file mode 100755
index 0000000..c07d986
--- /dev/null
+++ b/bin/slim
@@ -0,0 +1,3 @@
+#!/command/execlineb
+backtick -E dims { slurp }
+grim -g ${dims}
diff --git a/bin/statusline b/bin/statusline
new file mode 100755
index 0000000..948e8cb
--- /dev/null
+++ b/bin/statusline
@@ -0,0 +1,81 @@
+#!/usr/bin/env lua5.4
+
+local stdio = require('posix.stdio')
+local poll = require('posix.poll')
+
+function get_output(prog)
+ local f = assert(io.popen(prog))
+ local output = f:read('*a')
+ f:close()
+ return output
+end
+
+function volume()
+ local output = get_output('wpctl get-volume @DEFAULT_AUDIO_SINK@')
+ local muted = output:find('MUTED') ~= nil
+ local _, _, volume = output:find('^Volume: ([0-9.]+)')
+ volume = math.floor(volume * 100)
+
+ local symbol = 'ERROR'
+ if muted then
+ symbol = ' '
+ elseif volume == 0 then
+ symbol = ''
+ elseif volume <= 50 then
+ symbol = ''
+ else
+ symbol = ' '
+ end
+ return string.format('%s %d%%', symbol, volume)
+end
+
+function network()
+ local output = get_output('iwctl station wlan0 get-networks')
+ local _, _, ssid = output:find('>.- (.-) ')
+ return ssid and '󰖩 ' .. ssid or '󰖪 '
+end
+
+local current_brightness = 0
+
+function brightness()
+ return ' ' .. current_brightness .. '%'
+end
+
+function battery()
+ local f = io.open('/sys/class/power_supply/BAT0/capacity', 'r')
+ local capacity = tonumber(f:read('*a'))
+ f:close()
+ f = io.open('/sys/class/power_supply/BAT0/status', 'r')
+ local status = f:read('*a')
+ f:close()
+
+ local symbol = 'ERROR'
+ local i = capacity > 0 and (capacity - 1) // 10 or 0
+ if status == 'Discharging\n' then
+ symbol = ({"󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"})[i] -- 󰂃
+ elseif status == 'Charging\n' then
+ symbol = ({"󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"})[i]
+ elseif status == 'Full\n' then
+ symbol = "󰂄"
+ end
+ return symbol .. ' ' .. capacity .. '%'
+end
+
+function clock()
+ return os.date(' %b %d %H:%M:%S')
+end
+
+local brightness_file = assert(io.popen('brightctl -l'))
+
+while true do
+ if poll.rpoll(stdio.fileno(brightness_file), 1000) > 0 then
+ current_brightness = tonumber(brightness_file:read('l'))
+ end
+ io.write(string.format('all status %s | %s | %s | %s | %s\n',
+ volume(),
+ network(),
+ brightness(),
+ battery(),
+ clock()
+ ))
+end
diff --git a/bin/statusline.ha b/bin/statusline.ha
new file mode 100644
index 0000000..4eed68d
--- /dev/null
+++ b/bin/statusline.ha
@@ -0,0 +1,319 @@
+// SPDX-FileCopyrightText: 2024 Sam Nystrom <sam@samnystrom.dev>
+// SPDX-License-Identifier: ISC
+
+use encoding::utf8;
+use fmt;
+use fs;
+use io;
+use memio;
+use os;
+use os::exec;
+use strconv;
+use strings;
+use time;
+use time::date;
+
+def ROSEWATER: str = "#f5e0dc";
+def FLAMINGO: str = "#f2cdcd";
+def PINK: str = "#f5c2e7";
+def MAUVE: str = "#cba6f7";
+def RED: str = "#f38ba8";
+def MAROON: str = "#eba0ac";
+def PEACH: str = "#fab387";
+def YELLOW: str = "#f9e2af";
+def GREEN: str = "#a6e3a1";
+def TEAL: str = "#94e2d5";
+def SKY: str = "#89dceb";
+def SAPPHIRE: str = "#74c7ec";
+def BLUE: str = "#89b4fa";
+def LAVENDER: str = "#b4befe";
+def TEXT: str = "#cdd6f4";
+def SUBTEXT1: str = "#bac2de";
+def SUBTEXT0: str = "#a6adc8";
+def OVERLAY2: str = "#9399b2";
+def OVERLAY1: str = "#7f849c";
+def OVERLAY0: str = "#6c7086";
+def SURFACE2: str = "#585b70";
+def SURFACE1: str = "#45475a";
+def SURFACE0: str = "#313244";
+def BASE: str = "#1e1e2e";
+def MANTLE: str = "#181825";
+def CRUST: str = "#11111b";
+
+def FG: [_]str = [MAUVE, RED, YELLOW, GREEN, SAPPHIRE];
+def BG: str = BASE;
+def TXT: str = BASE;
+
+type error = !(exec::error | !exec::exit_status | utf8::invalid | strconv::invalid | strconv::overflow | io::error | fs::error);
+
+fn strerror(err: error) const str = {
+ match (err) {
+ case let err: io::error =>
+ return io::strerror(err);
+ case let err: fs::error =>
+ return fs::strerror(err);
+ case let err: exec::error =>
+ return exec::strerror(err);
+ case let err: !exec::exit_status =>
+ return "command exited nonzero";
+ case utf8::invalid =>
+ return "invalid UTF-8";
+ case strconv::invalid =>
+ return "invalid number";
+ case strconv::overflow =>
+ return "invalid number";
+ };
+};
+
+type json = ([](str, json) | []json | str | f64 | int | bool | void);
+
+fn print_json_str(out: io::handle, s: str) (void | io::error) = {
+ fmt::fprint(out, "\"")?;
+ let it = strings::iter(s);
+ for (let r => strings::next(&it)) {
+ switch (r) {
+ case '\b' =>
+ fmt::fprint(out, "\b")?;
+ case '\f' =>
+ fmt::fprint(out, "\f")?;
+ case '\n' =>
+ fmt::fprint(out, "\n")?;
+ case '\r' =>
+ fmt::fprint(out, "\r")?;
+ case '\t' =>
+ fmt::fprint(out, "\t")?;
+ case '\"' =>
+ fmt::fprint(out, "\\\"")?;
+ case '\\' =>
+ fmt::fprint(out, "\\\\")?;
+ case =>
+ if (r: u32 < 0x20) {
+ fmt::fprintf(out, "\\u{:.4x}", r: u32)?;
+ } else {
+ fmt::fprint(out, r)?;
+ };
+ };
+ };
+ fmt::fprint(out, "\"")?;
+};
+
+fn dump(out: io::handle, value: json) (void | io::error) = {
+ match (value) {
+ case let obj: [](str, json) =>
+ fmt::fprint(out, "{")?;
+ for (let i = 0z; i < len(obj); i += 1) {
+ if (i > 0) fmt::fprint(out, ",")?;
+ print_json_str(out, obj[i].0)?;
+ fmt::fprint(out, ":")?;
+ dump(out, obj[i].1)?;
+ };
+ fmt::fprint(out, "}")?;
+ case let arr: []json =>
+ fmt::fprint(out, "[")?;
+ for (let i = 0z; i < len(arr); i += 1) {
+ if (i > 0) fmt::fprint(out, ",")?;
+ dump(out, arr[i])?;
+ };
+ fmt::fprint(out, "]")?;
+ case let s: str =>
+ print_json_str(out, s)?;
+ case let x: f64 =>
+ fmt::fprint(out, x)?;
+ case let n: int =>
+ fmt::fprint(out, n)?;
+ case let b: bool =>
+ fmt::fprint(out, b)?;
+ case void =>
+ fmt::fprint(out, "null")?;
+ };
+};
+
+type span = (str, (str | void), (str | void));
+
+fn spans(spans: (str | span)...) str = {
+ let buf = memio::dynamic();
+ defer io::close(&buf)!;
+ for (let sp .. spans) {
+ match (sp) {
+ case let s: str =>
+ fmt::fprint(&buf, s)!;
+ case let span: span =>
+ let (s, fg, bg) = span;
+ fmt::fprint(&buf, "<span")!;
+ match (fg) {
+ case let fg: str =>
+ fmt::fprintf(&buf, " foreground=\"{}\"", fg)!;
+ case void => void;
+ };
+ match (bg) {
+ case let bg: str =>
+ fmt::fprintf(&buf, " background=\"{}\"", bg)!;
+ case void => void;
+ };
+ fmt::fprintf(&buf, ">{}</span>", s)!;
+ };
+ };
+ return strings::dup(memio::string(&buf)!);
+};
+
+fn run(name: str, args: str...) (str | error) = {
+ let cmd = exec::cmd(name, args...)?;
+ let pipe = exec::pipe();
+ exec::addfile(&cmd, os::stdout_file, pipe.1);
+ let proc = exec::start(&cmd)?;
+ io::close(pipe.1)?;
+ let output = io::drain(pipe.0)?;
+ io::close(pipe.0)?;
+ let status = exec::wait(&proc)?;
+ exec::check(&status)?;
+ return strings::fromutf8(output)?;
+};
+
+fn get_volume() ((uint, bool) | error) = {
+ let output = run("wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@")?;
+ defer free(output);
+ let muted = strings::contains(output, "MUTED");
+ let volume = strconv::stof64(strings::cut(strings::cut(strings::rtrim(output), " ").1, " ").0)?;
+ return ((volume * 100.0): uint, muted);
+};
+
+fn volume() (void | error) = {
+ let (volume, muted) = get_volume()?;
+ let symbol = if (muted) " " else if (volume == 0) "" else if (volume <= 50) "" else " ";
+ let text = fmt::asprintf(" {} {}%", symbol, volume);
+ defer free(text);
+ let text = spans((" ", FG[0], BG), (text, TXT, FG[0]), (" ", BG, FG[0]));
+ defer free(text);
+ dump(os::stdout, [("full_text", text), ("markup", "pango"), ("separator_block_width", 0)]: [](str, json))!;
+};
+
+fn network() (void | error) = {
+ let output = run("iwctl", "station", "wlan0", "get-networks")?;
+ defer free(output);
+ let line = "";
+ let iter = strings::tokenize(output, "\n");
+ for (let s => strings::next_token(&iter)) {
+ if (strings::contains(s, '>')) {
+ line = s;
+ break;
+ };
+ };
+ let ssid = strings::cut(strings::cut(strings::cut(line, " ").1, " ").1, " ").0;
+ let text = if (ssid != "")
+ fmt::asprintf(" 󰖩 {}", ssid)
+ else
+ fmt::asprint(" 󰖪 ");
+ defer free(text);
+ let text = spans(("", FG[1], BG), (text, TXT, FG[1]), (" ", BG, FG[1]));
+ defer free(text);
+ dump(os::stdout, [("full_text", text), ("markup", "pango"), ("separator_block_width", 0)]: [](str, json))!;
+};
+
+fn get_brightness() (uint | error) = {
+ let output = run("brightctl")?;
+ defer free(output);
+ return strconv::stou(strings::rtrim(output))?;
+};
+
+fn brightness() (void | error) = {
+ let text = fmt::asprintf("  {}%", get_brightness()?);
+ defer free(text);
+ let text = spans(("", FG[2], BG), (text, TXT, FG[2]), (" ", BG, FG[2]));
+ defer free(text);
+ dump(os::stdout, [("full_text", text), ("markup", "pango"), ("separator_block_width", 0)]: [](str, json))!;
+};
+
+fn battery() (void | error) = {
+ let file = os::open("/sys/class/power_supply/BAT0/capacity")?;
+ let content = io::drain(file)?;
+ io::close(file)?;
+ defer free(content);
+ let capacity = strconv::stou(strings::rtrim(strings::fromutf8(content)?))?;
+
+ let file = os::open("/sys/class/power_supply/BAT0/status")?;
+ let content = io::drain(file)?;
+ io::close(file)?;
+ defer free(content);
+ let status = strings::rtrim(strings::fromutf8(content)?);
+
+ let symbol = "ERROR";
+ let i = if (capacity == 0) 0u else (capacity - 1) / 10;
+ switch (status) {
+ case "Discharging" =>
+ symbol = ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"][i]; // 󰂃
+ case "Charging" =>
+ symbol = ["󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"][i];
+ case "Full" =>
+ symbol = "󰂄";
+ case => void;
+ };
+ let text = fmt::asprintf(" {} {}%", symbol, capacity);
+ defer free(text);
+ let text = spans(("", FG[3], BG), (text, TXT, FG[3]), (" ", BG, FG[3]));
+ defer free(text);
+ dump(os::stdout, [("full_text", text), ("markup", "pango"), ("separator_block_width", 0)]: [](str, json))!;
+};
+
+fn clock() (void | error) = {
+ // this used to be %-d which disabled padding, but hare doesn't support it
+ let buf = memio::dynamic();
+ defer io::close(&buf)!;
+ date::format(&buf, "  %b %d %H:%M:%S ", &date::now())?;
+ let buf_text = memio::string(&buf)!;
+ let text = spans(("", FG[4], BG), (buf_text, TXT, FG[4]), (" ", FG[4], BG));
+ defer free(text);
+ dump(os::stdout, [("full_text", text), ("markup", "pango"), ("separator_block_width", 0)]: [](str, json))!;
+};
+
+export fn main() void = {
+ dump(os::stdout, [("version", 1), ("click_events", false)]: [](str, json))!;
+ fmt::println()!;
+ fmt::println("[")!;
+
+ let last_vol = 0u;
+ let last_brt = 0u;
+ let last_mute = false;
+ let sleep_dur = time::SECOND;
+
+ for (true) {
+ let (vol, mute) = match (get_volume()) {
+ case let x: (uint, bool) =>
+ yield x;
+ case let err: error =>
+ fmt::errorln("statusbar: error:", strerror(err))!;
+ yield (last_vol, last_mute);
+ };
+ let brt = match (get_brightness()) {
+ case let x: uint =>
+ yield x;
+ case let err: error =>
+ fmt::errorln("statusbar: error:", strerror(err))!;
+ yield last_brt;
+ };
+
+ if (last_vol != vol || last_mute != mute || last_brt != brt) {
+ sleep_dur = time::MILLISECOND * 100;
+ } else if (sleep_dur < time::SECOND) {
+ sleep_dur += time::MILLISECOND * 100;
+ } else {
+ sleep_dur = time::SECOND;
+ };
+ last_vol = vol;
+ last_mute = mute;
+ last_brt = brt;
+
+ let segments: []*fn() (void | error) = [&volume, &network, &brightness, &battery, &clock];
+ fmt::print("[")!;
+ for (let i = 0z; i < len(segments); i += 1) {
+ if (i > 0) fmt::print(",")!;
+ match (segments[i]()) {
+ case void => void;
+ case let err: error =>
+ fmt::errorln("statusbar: error:", strerror(err))!;
+ };
+ };
+ fmt::println("],")!;
+
+ time::sleep(sleep_dur);
+ };
+};
diff --git a/bin/statusbar b/bin/statusline.sh
index 4dfafec..61eb799 100755
--- a/bin/statusbar
+++ b/bin/statusline.sh
@@ -51,21 +51,23 @@ clock() {
printf ' %s' "$(date '+%b %-d %H:%M:%S')"
}
-# Make the volume and brightness sections appear to update more often by
-# heuristically detecting when the user is changing them.
+# Make the volume and brightness sections appear to update in real time by
+# polling more frequently when the user is changing them.
last_vol=
last_brt=
-sleep_for=1
+sleep_for=10
while true; do
vol="$(volume)"
brt="$(brightness)"
if [ "$last_vol" != "$vol" ] || [ "$last_brt" != "$brt" ]; then
- sleep_for=0.1
- elif [ ${sleep_for%%.*} != 1 ]; then
- sleep_for=$((sleep_for+0.1))
- else
sleep_for=1
+ elif [ $sleep_for != 10 ]; then
+ sleep_for=$((sleep_for+1))
+ else
+ sleep_for=10
fi
+ last_vol="$vol"
+ last_brt="$brt"
printf '%s | %s | %s | %s | %s\n' \
"$vol" \
@@ -73,5 +75,5 @@ while true; do
"$brt" \
"$(battery)" \
"$(clock)"
- sleep $sleep_for
+ sleep $((sleep_for/10)).$((sleep_for%10))
done
diff --git a/bin/waylock b/bin/waylock
new file mode 100755
index 0000000..39e5c08
--- /dev/null
+++ b/bin/waylock
@@ -0,0 +1,2 @@
+#!/command/execlineb -s0
+/usr/bin/waylock -init-color 0x1e1e2e -input-color 0xb4befe -fail-color 0xf38ba8 ${@}
diff --git a/bin/wmenu b/bin/wmenu
new file mode 100755
index 0000000..675b31b
--- /dev/null
+++ b/bin/wmenu
@@ -0,0 +1,13 @@
+#!/bin/execlineb -s0
+multisubstitute {
+ define bg "1e2030"
+ define fg "cad3f5"
+ define accent "8aadf4"
+}
+emptyenv -P
+/usr/bin/wmenu
+ -f "Fira Code Nerd Font 12"
+ -N ${bg} -n ${fg}
+ -M ${accent} -m ${bg}
+ -S ${accent} -s ${bg}
+ ${@}
diff --git a/bin/wmenu-run b/bin/wmenu-run
new file mode 100755
index 0000000..84e85f2
--- /dev/null
+++ b/bin/wmenu-run
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec $(IFS=:; find -L ${PATH} -type f -perm -555 -exec basename -a '{}' + | wmenu -p run:)
diff --git a/bin/wmenu-yesno b/bin/wmenu-yesno
new file mode 100755
index 0000000..e719215
--- /dev/null
+++ b/bin/wmenu-yesno
@@ -0,0 +1,4 @@
+#!/command/execlineb -s1
+backtick -ED No answer { heredoc 0 "Yes\nNo\n" wmenu -p ${1} }
+if -t { test ${answer} = Yes }
+exec ${@}