diff options
| author | Sam Nystrom <sam@samnystrom.dev> | 2024-10-15 13:29:10 -0400 |
|---|---|---|
| committer | Sam Nystrom <sam@samnystrom.dev> | 2024-10-15 13:29:10 -0400 |
| commit | 2c98bbacc8db3b251e1679f9da84cf1f5ed5726a (patch) | |
| tree | 10d44c9b82d275d66d880ff8ec33992cddda12b5 | |
| parent | c8c79e6c6a5c4bb1e0ba44f309ce5ae612f97e1c (diff) | |
update ~/bin
| -rwxr-xr-x | bin/av1ify | 17 | ||||
| -rwxr-xr-x | bin/bun | 2 | ||||
| -rwxr-xr-x | bin/dwl | 4 | ||||
| -rw-r--r-- | bin/ff-catwalk.c | 50 | ||||
| -rwxr-xr-x | bin/ffimv | 9 | ||||
| -rwxr-xr-x | bin/harespec | 3 | ||||
| -rwxr-xr-x | bin/hiprompt-wmenu | 28 | ||||
| -rw-r--r-- | bin/infinity.c | 216 | ||||
| -rwxr-xr-x | bin/init | 51 | ||||
| -rwxr-xr-x | bin/jq | 2 | ||||
| -rwxr-xr-x | bin/pastebin | 5 | ||||
| -rwxr-xr-x | bin/readmail | 19 | ||||
| -rwxr-xr-x | bin/s6-rclocal-compile | 13 | ||||
| -rwxr-xr-x | bin/s6-rclocal-make-logger | 14 | ||||
| -rwxr-xr-x | bin/slim | 3 | ||||
| -rwxr-xr-x | bin/statusline | 81 | ||||
| -rw-r--r-- | bin/statusline.ha | 319 | ||||
| -rwxr-xr-x | bin/statusline.sh (renamed from bin/statusbar) | 18 | ||||
| -rwxr-xr-x | bin/waylock | 2 | ||||
| -rwxr-xr-x | bin/wmenu | 13 | ||||
| -rwxr-xr-x | bin/wmenu-run | 2 | ||||
| -rwxr-xr-x | bin/wmenu-yesno | 4 |
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} @@ -0,0 +1,2 @@ +#!/command/execlineb -s0 +nix run --offline nixpkgs#bun -- ${@} @@ -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 @@ -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 ${@} |
