summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2020-01-26 17:16:07 +0100
committerRobert Günzler <r@gnzler.io>2020-01-26 18:00:59 +0100
commitf5d56602df70950de6b7d40966b00c9939c81763 (patch)
treeb50ffeb5496fe3525cc1bab2629389ced95435b7 /bin
Intial commit of v2
See https://drewdevault.com/2019/12/30/dotfiles.html
Diffstat (limited to 'bin')
-rwxr-xr-xbin/askpass8
-rwxr-xr-xbin/chrome-extension-dl83
-rwxr-xr-xbin/clone127
-rwxr-xr-xbin/colorpick6
-rwxr-xr-xbin/find-rust-target-dir16
-rwxr-xr-xbin/gh114
-rwxr-xr-xbin/grimshot146
-rwxr-xr-xbin/hibiki/mount-solus-backup35
-rwxr-xr-xbin/hibiki/soundswitch41
-rwxr-xr-xbin/hr20
-rwxr-xr-xbin/kp75
-rwxr-xr-xbin/lock32
-rwxr-xr-xbin/lowbat19
-rwxr-xr-xbin/mega33
-rwxr-xr-xbin/menu28
-rwxr-xr-xbin/mesoninit191
-rwxr-xr-xbin/now6
-rwxr-xr-xbin/obsctl121
-rwxr-xr-xbin/open59
-rwxr-xr-xbin/preview54
-rwxr-xr-xbin/qemuu56
-rwxr-xr-xbin/randgen8
-rwxr-xr-xbin/semver88
-rwxr-xr-xbin/soundswitch_dmenu133
-rwxr-xr-xbin/streamon7
-rwxr-xr-xbin/sway-run47
-rwxr-xr-xbin/swq139
-rwxr-xr-xbin/termify10
-rwxr-xr-xbin/todo128
-rwxr-xr-xbin/trash53
-rwxr-xr-xbin/url-launcher5
-rwxr-xr-xbin/usb2sys36
-rwxr-xr-xbin/wsudo44
33 files changed, 1968 insertions, 0 deletions
diff --git a/bin/askpass b/bin/askpass
new file mode 100755
index 0000000..fe02ef4
--- /dev/null
+++ b/bin/askpass
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+echo "${ASKPASS_PROMPT:-Enter your password}" | \
+    wofi -k /dev/null \
+        --height 50 \
+        --dmenu \
+        --password \
+        --prompt ' Password'
diff --git a/bin/chrome-extension-dl b/bin/chrome-extension-dl
new file mode 100755
index 0000000..a40b519
--- /dev/null
+++ b/bin/chrome-extension-dl
@@ -0,0 +1,83 @@
+#!/usr/bin/env sh
+# Download extensions from chrome webstore
+#
+# source: https://gitlab.com/snippets/1888145
+
+[ -z "$EXTENSIONS_DIR" ] && EXTENSIONS_DIR="${extensions_dir:=${XDG_DATA_HOME:=$HOME/.local/share}/chrome-extensions}"
+
+magenta="\033[35;1m"
+nc="\033[m"
+
+die() { printf 'Error: %s\n' "$1" >&2; exit 1; }
+
+parse_url(){
+    extension_id="$(echo "$1" | grep -oP '[_a-zA-Z0-9]{30,}')"
+    download_extension
+}
+
+download_extension(){
+    extension_file="$(mktemp "${TMPDIR:-/tmp/}$extension_id.XXXXXXXXXXXX.zip")"
+    printf "\nDownloading extension %s\n" "$extension_id"
+    curl -fsSL "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=49.0&x=id%3D$extension_id%26installsource%3Dondemand%26uc" --output "$extension_file" || die "Unable to download extension"
+    extract_extension
+}
+
+extract_extension(){
+    nohup rm -fr "$extensions_dir/${extension_id:?}" > /dev/null 2>&1 ||:
+    mkdir -p "$extensions_dir/$extension_id"
+    nohup unzip -q "$extension_file" -d "$extensions_dir/$extension_id" > /dev/null 2>&1
+    printf "\n Extension installed to directory %s\n" "$extensions_dir/$extension_id"
+    install_help
+}
+
+update_extensions(){
+    cd "$extensions_dir" || die "Extensions directory does not exist"
+    for extension in *; do
+        if [ -d "$extension" ]; then
+            printf "\nUpdating extension %s\n" "$extension"
+            extension_id="$extension"
+            download_extension
+        fi
+    done
+}
+
+general_help(){
+    echo -e "
+    ${magenta}NAME${nc}
+    $(basename "$0") - install chrome extensions
+
+    ${magenta}SYNOPSIS${nc}
+    $(basename "$0") [webstore url]
+    $(basename "$0") update
+
+    ${magenta}DESCRIPTION${nc}
+    $(basename "$0") Download extensions from chrome webstore.
+
+    set EXTENSIONS_DIR to change extensions folder."
+}
+
+install_help(){
+    echo -e "
+    ${magenta}Install Guide:${nc}
+    1. Visit chrome://extensions in your browser.
+    2. Ensure that the Developer mode checkbox in the top right-hand corner is checked.
+    3. Click Load unpacked extension... to pop up a file-selection dialog.
+    4. Navigate to $extensions_dir/, and select it."
+}
+
+main(){
+    if [ "$#" -eq 0 ]; then
+        general_help && install_help
+    else
+        for input in "$@"; do
+            shift
+            case "$input" in
+                -h|--h*) general_help && install_help ;;
+                u*) update_extensions ;;
+                *)  parse_url "$input" ;;
+            esac
+        done
+    fi
+}
+
+main "$@"
diff --git a/bin/clone b/bin/clone
new file mode 100755
index 0000000..1882a25
--- /dev/null
+++ b/bin/clone
@@ -0,0 +1,127 @@
+#!/bin/bash
+#
+# To use this create a directory that you want to use for git repos, e.g. $HOME/git and copy/symlink this script into it.
+# Then you can use it like this:
+#
+# ./clone github.com/robertgzr/dotfiles
+# ./clone https://bitbucket.org/foo/bar
+#
+# If you don't have gitconfig set up like I have (using aliases for github/bitbucket ssh access) this won't really work
+set -e
+
+SHALLOW=0
+RECURSE_SUBMODULES=
+TEMP="$(mktemp -d -p /tmp clone.XXXXXXXXXX)"
+
+URL=$1
+if [[ -z "${URL}" ]]; then
+    fail "no input"
+else
+    URL="${URL//(http|https):\/\//}"
+fi
+
+make_dirs() {
+    local url=$1
+
+    if [[ -d "${url}" ]]
+    then
+        echo "exists: ${url}"
+        return
+    fi
+
+    confirm "create ${url}"
+    mkdir -p "${url}"
+}
+
+clone() {
+    local in=$1
+    local to=$2
+    IFS='/' read -r -a url <<< "${in}"
+
+    case "${url[0]}" in
+        github.com ) ssh_clone gh "${in}" "${to}";;
+        gitlab.com ) ssh_clone gl "${in}" "${to}";;
+        bitbucket.org ) ssh_clone bb "${in}" "${to}";;
+        git.sr.ht ) ssh_clone srht "${in}" "${to}";;
+        * ) http_clone "${url[@]// /\/}" "${to}";;
+    esac
+}
+
+ssh_clone() {
+    local prefix=$1
+    local in=$2
+    local to=$3
+    IFS='/' read -r -a url <<< "${in}"
+    local ssh_url="${prefix}:${url[1]}/${url[2]}"
+
+    # confirm "clone $ssh_url"
+    git_clone "${ssh_url}" "${to}"
+}
+
+http_clone() {
+    local to=$2
+    local http_url="https://${URL}"
+
+    # confirm "clone $http_url"
+    git_clone "${GIT_FLAGS}" "${http_url}" "${to}"
+}
+
+git_clone() {
+    declare -a GIT_FLAGS
+    if [[ ${SHALLOW} -ne 0 ]]; then
+        GIT_FLAGS+="--depth=${SHALLOW}"
+    fi
+    if [[ ! -z ${RECURSE_SUBMODULES} ]]; then
+        GIT_FLAGS+="--recurse-submodules"
+    fi
+    git clone "${GIT_FLAGS[@]}" "${@}"
+}
+
+fail() {
+    echo "${1:-error}"
+    exit ${2:-1}
+}
+
+confirm() {
+    echo -n "${1:-'do something'}, ok (y/n)? "
+    read yn
+    if [[ "$yn" = "${yn#[Yy]}" ]]; then
+        fail "ok, exiting" 0
+    fi
+}
+
+usage() {
+    echo "usage: clone [-s][-r] URL"
+    echo ""
+    echo -e " -s\tshallow clone"
+    echo -e " -r\trecurse submodules"
+    echo -e " URL\texample.com/user/repo"
+    echo -e "\thttp[s]://example.com/user/repo"
+    exit 0
+}
+
+while getopts ":hsr" opt; do
+    case ${opt} in
+        s )
+            echo "Shallow clone"
+            SHALLOW=1
+            ;;
+        r )
+            echo "Recurse submodules"
+            RECURSE_SUBMODULES=1
+            ;;
+        h ) 
+            usage
+            ;;
+        \? ) 
+            usage
+            ;;
+    esac
+done
+shift $((OPTIND -1))
+
+clone "${URL}" "${TEMP}/${URL}" || fail
+
+target="${PWD}/$(dirname "${URL}")"
+install -d "${target}" || fail
+mv "${TEMP}/${URL}" "${target}" || fail
diff --git a/bin/colorpick b/bin/colorpick
new file mode 100755
index 0000000..bb099cd
--- /dev/null
+++ b/bin/colorpick
@@ -0,0 +1,6 @@
+#!/bin/sh
+# dependencies: grim slurp wl-clipboard
+# src: https://git.sr.ht/~sircmpwn/dotfiles/tree/f9e7e88a72c2b87e5ab823205cb3a3aa8f3babd0/bin/colorpick
+color=$(grim -g "$(slurp -p -b 00000000)" - | convert - txt:- | awk 'END{print $3}')
+wl-copy "$color"
+notify-send "Color copied to clipboard" "$color" --icon '/usr/share/icons/Adwaita/scalable/actions/color-select-symbolic.svg'
diff --git a/bin/find-rust-target-dir b/bin/find-rust-target-dir
new file mode 100755
index 0000000..b59a2b4
--- /dev/null
+++ b/bin/find-rust-target-dir
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+is_rust_root() {
+    [ -f $1/Cargo.toml ] || return 1
+    [ -d $1/target ] || return 1
+    return 0
+}
+
+inspect() {
+    for dir in `fd -t d -d 1 '' "${1}"`; do
+        is_rust_root ${dir} && { echo ${dir}; du -h -d 0; echo -e "\n"; continue; }
+        inspect ${dir}
+    done
+}
+
+inspect ${1:-$PWD}
diff --git a/bin/gh b/bin/gh
new file mode 100755
index 0000000..66b7767
--- /dev/null
+++ b/bin/gh
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+set -e -o pipefail
+
+CURL='curl -sSL'
+JQ='jq -r'
+
+GH_ACCESS_TOKEN=${GH_ACCESS_TOKEN:-`cat $DOT_DIR/GH_TOKEN_GH_SCRIPT`}
+
+usage() {
+    printf "usage: gh [-v] [commands] [options]\n"
+    printf "\ncommands:\n"
+    printf " release-latest REPO\t\t prints the latest version tag of REPO\n"
+    printf " release-url REPO TAG\t prints the url to the release TAG of REPO\n"
+    printf " tag-latest REPO\t\t prints the latest version tag of REPO\n"
+    printf " gql QUERY\t\t sends a query to the v4 graphql API\n"
+    printf "\n"
+}
+
+err() {
+    echo >&2 "error: $@"
+}
+
+has_cmd() {
+    if ! command -v $1 1,2&>/dev/null; then
+        err "$s not found"
+        return 1
+    fi
+}
+
+gql() {
+    local q=$1
+    local res=`$CURL https://api.github.com/graphql \
+        -X POST \
+        -H "Authorization: bearer $GH_ACCESS_TOKEN" \
+        -H "Content-Type: application/json" \
+        -d "{\"query\":\"query{ $q }\"}"`
+
+    [[ $? -ne 0 ]] && {
+        err `echo $res | $JQ '.message'`
+        err "request was" "{\"query\":\"query{ $q }\"}"
+        exit 1
+    }
+    [[ -z "$res" ]] && {
+        err "empty response"
+        exit 1
+    }
+    local errors=`echo $res | $JQ '.errors?'`
+    [[ $errors -ne "null" ]] && {
+        err "$errors"
+        err "request was" "{\"query\":\"query{ $q }\"}"
+        exit 1
+    }
+    echo $res
+}
+
+if [ -z "$GH_ACCESS_TOKEN" ]; then
+    err "no github access token"
+    exit 1
+fi
+
+has_cmd jq
+
+cmd="$1"; opt="$2"
+case $cmd in
+
+    gql)
+        if [[ -z $opt ]]; then
+            err "no QUERY found"
+            exit 1
+        fi
+        gql "$opt" | jq '.'
+        exit 0
+        ;;
+
+    release-latest)
+        if [[ -z $opt ]]; then
+            err "no REPO found"
+            exit 1
+        fi
+        opt=(${opt//\// })
+        gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){ releases(first:1, orderBy:{field:CREATED_AT, direction:DESC}){ nodes{tag{name}}} }" | $JQ '.data.repository.releases.nodes[].tag.name'
+        exit 0
+        ;;
+
+    tag-latest)
+        if [[ -z $opt ]]; then
+            err "no REPO found"
+            exit 1
+        fi
+        opt=(${opt//\// })
+        gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){ refs(first: 1, orderBy:{field:TAG_COMMIT_DATE, direction:DESC}, refPrefix:\\\"refs/tags/\\\"){ nodes{name}} }" | $JQ '.data.repository.refs.nodes[].name'
+        exit 0
+        ;;
+
+
+    release-url)
+        if [[ -z $opt ]]; then
+            err "no REPO found"
+            exit 1
+        fi
+        if [[ -z $3 ]]; then
+            err "no TAG found"
+        fi
+        opt=(${opt//\// })
+        gql "repository(owner:\\\"${opt[0]}\\\",name:\\\"${opt[1]}\\\"){release(tagName:\\\"$3\\\"){url}}" | $JQ '.data.repository.release.url'
+        exit 0
+        ;;
+
+    *)
+        usage
+        exit 0
+        ;;
+esac
diff --git a/bin/grimshot b/bin/grimshot
new file mode 100755
index 0000000..819e7c5
--- /dev/null
+++ b/bin/grimshot
@@ -0,0 +1,146 @@
+#!/bin/sh
+
+## Grimshot: a helper for screenshots within sway
+## Requirements:
+##  - `grim`: screenshot utility for wayland
+##  - `slurp`: to select an area
+##  - `wf-recorder`: screen-recording utility
+##  - `swaymsg`: to read properties of current window
+##  - `wl-copy`: clipboard utility
+##  - `jq`: json uliity to parse swaymsg output
+##  - `notify-send`: to show notifications
+##  - `mktemp`: to create a temporary file
+## Those are needed to be installed, if unsure, run `grimshot check`
+##
+## Examples:
+## `grimshot copy win` - to copy current window
+## `grimshot save area` - to select area and save it to default file (Pictures/Grimshot-$datetime.png)
+## `grimshot save screen ~/screenshot.png` - to save screenshot under ~/screenshot.png
+## `grimshot | grimshot -h` - usage
+## `grimshot check` - verify if tools are installed
+
+ACTION=${1:-usage}
+SUBJECT=${2:-screen}
+FILE=${3:-$(xdg-user-dir PICTURES)/screenshots/$(now).png}
+case "$ACTION" in
+  usage|help|-h|--help)
+  echo "Usage:"
+  echo "  grimshot copy|save|rec|rec-copy win|screen|area [FILE]"
+  echo "Troubleshoot:"
+  echo "  grimshot check"
+  exit
+esac
+
+notify() {
+  # notify-send -i "${TMP:-${FILE}}" -t 3000 -a grimshot "$@"
+  notify-send -i "${TMP:-${FILE}}" -t 3000 "$@"
+}
+notifyOk() {
+  TITLE=${2:-"Screenshot"}
+  MESSAGE=${1:-"OK"}
+  notify "$TITLE" "$MESSAGE"
+}
+notifyError() {
+  TITLE=${2:-"Screenshot"}
+  MESSAGE=${1:-"Error taking screenshot with grim"}
+  notify -u critical "$TITLE" "$MESSAGE"
+}
+
+die() {
+  MSG=${1:-Bye}
+  notifyError "Error: $MSG"
+  exit 2
+}
+
+check() {
+  COMMAND=$1
+  command -v "$COMMAND" > /dev/null 2>&1
+  if [ $? ]; then
+    RESULT="OK"
+  else
+    RESULT="NOT FOUND"
+  fi
+  echo "   $COMMAND: $RESULT"
+}
+
+takeScreenshot() {
+  FILE=$1
+  GEOM=$2
+  if [ -z "$GEOM" ]; then
+    grim "$FILE" || die "Unable to invoke grim"
+  else
+    grim -g "$GEOM" "$FILE" || die "Unable to invoke grim"
+  fi
+}
+
+recordScreen() {
+  FILE=$1
+  GEOM=$2
+  if [ -z "$GEOM" ]; then
+    wf-recorder -f "$FILE" || die "Unable to invoke wf-recorder"
+  else
+    wf-recorder -g "$GEOM" -f "$FILE" || die "Unable to invoke wf-recorder"
+  fi
+}
+
+if [ "$ACTION" = "check" ] ; then
+  echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
+  check grim
+  check slurp
+  check wf-recorder
+  check swaymsg
+  check wl-copy
+  check jq
+  check notify-send
+  check mktemp
+  exit
+elif [ "$SUBJECT" = "area" ] ; then
+  GEOM=$(slurp -d)
+  WHAT="Area"
+elif [ "$SUBJECT" = "win" ] ; then
+  FOCUSED=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?, .floating_nodes[]?) | select(.focused)')
+  GEOM=$(echo "$FOCUSED" | jq -r '.rect | "\(.x),\(.y) \(.width)x\(.height)"')
+  APP_ID=$(echo "$FOCUSED" | jq -r '.app_id')
+  WHAT="$APP_ID window"
+elif [ "$SUBJECT" = "screen" ] ; then
+  GEOM=""
+  WHAT="Screen"
+else
+  die "Unknown subject to take a screen shot from" "$SUBJECT"
+fi
+
+if [ "$ACTION" = "copy" ] ; then
+  TMP=$(mktemp) || die "Unable to create temp file: is mktemp installed?"
+  takeScreenshot "$TMP" "$GEOM"
+  wl-copy --type image/png < "$TMP"  || die "Clipboard error"
+  notifyOk "$WHAT copied to buffer"
+  rm "$TMP"
+elif [ "$ACTION" = "save" ] ; then
+  takeScreenshot "$FILE" "$GEOM"
+  if [ $? ]; then
+    TITLE="Screenshot of $SUBJECT"
+    MESSAGE=$(basename "$FILE")
+    notifyOk "$MESSAGE" "$TITLE"
+  else
+    notifyError "Error taking screenshot with grim"
+  fi
+elif [ "$ACTION" = "rec" ] ; then
+  echo "$FILE" | awk '/png$/{exit 0}' && \
+    FILE=$(xdg-user-dir VIDEOS)/grimshot/$(now).mp4
+  recordScreen "$FILE" "$GEOM"
+  if [ $? ]; then
+    TITLE="Screen recording of $SUBJECT"
+    MESSAGE=$(basename "$FILE")
+    notifyOk "$MESSAGE" "$TITLE"
+  else
+    notifyError "Error recording screen with wf-recorder"
+  fi
+elif [ "$ACTION" = "rec-copy" ] ; then
+  TMP=$(mktemp --suffix '.mp4') || die "Unable to create temp file: is mktemp installed?"
+  recordScreen "$TMP" "$GEOM"
+  wl-copy --type video/mp4 < "$TMP"  || die "Clipboard error"
+  notifyOk "$WHAT copied to buffer"
+  rm "$TMP"
+else
+  die "Unknown action" "$ACTION"
+fi
diff --git a/bin/hibiki/mount-solus-backup b/bin/hibiki/mount-solus-backup
new file mode 100755
index 0000000..7aaf44b
--- /dev/null
+++ b/bin/hibiki/mount-solus-backup
@@ -0,0 +1,35 @@
+#!/bin/env bash
+
+D_SQUASHFS="solus_2019-12-23.squashfs"
+D_RAW="${D_SQUASHFS%.squashfs}.img"
+
+mount() {
+	udisksctl mount -b /dev/disk/by-partlabel/rTranscend
+
+	mkdir -p /tmp/mnt-solus-squashfs
+	sudo mount /run/media/robert/rTranscend/${D_SQUASHFS} /tmp/mnt-solus-squashfs
+
+	sudo losetup --partscan /dev/loop1 /tmp/mnt-solus-squashfs/${D_RAW}
+	sudo pvs
+	sudo vgchange -a y SolusSystem
+
+	mkdir -p /tmp/mnt-solus
+	sudo mount -o ro /dev/SolusSystem/Root /tmp/mnt-solus
+
+	ls /tmp/mnt-solus
+}
+
+unmount() {
+	sudo umount /tmp/mnt-solus
+	sudo vgchange -a n SolusSystem
+	sudo losetup -d /dev/loop1
+	sudo umount /tmp/mnt-solus-squashfs
+	udisksctl unmount -b /dev/disk/by-partlabel/rTranscend
+}
+
+set -x
+
+case "$1" in
+	-u) unmount ;;
+	*) mount ;;
+esac
diff --git a/bin/hibiki/soundswitch b/bin/hibiki/soundswitch
new file mode 100755
index 0000000..9ba73c9
--- /dev/null
+++ b/bin/hibiki/soundswitch
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -ex
+
+global=
+focused=
+profile=
+
+while getopts fgp: name; do
+	case $name in
+		g) global=1 ;;
+		f) focused=1 ;;
+		p) profile=1 ;;
+		?) printf "usage: %s [-gf]\n" "$0" >&2; exit 2 ;;
+	esac
+done
+
+if [ "$#" -lt 1 ]; then
+	printf "global\nfocused\nprofile\n" | menu | {
+		read -r line
+		case "${line}" in
+			global) exec "$0" -g ;;
+			focused) exec "$0" -f ;;
+			profile) exec "$0" -p ;;
+		esac
+	}
+fi
+
+if [ -n "${global}" ]; then
+	pactl list short sinks | awk '{print $2}' | menu | xargs pactl set-default-sink
+	exit 0
+fi
+
+if [ -n "${focused}" ]; then
+	printf "not implemented\n" >&2
+	exit 1
+fi
+
+if [ -n "${profile}" ]; then
+	printf "not implemented\n" >&2
+	exit 1
+fi
diff --git a/bin/hr b/bin/hr
new file mode 100755
index 0000000..9c421fc
--- /dev/null
+++ b/bin/hr
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+str="$1"
+[[ -n $str ]] && str=" $str "
+getcols() {
+    if command -v tput &>/dev/null; then
+        tput cols
+    else
+        stty size | awk '{print $2}'
+    fi
+}
+COLS=$((${COLUMNS:-`getcols`} - ${#str} - 2))
+start=$'\e(0'; end=$'\e(B'; line="qqqqqqqqqqqqqqqq"
+while ((${#line}<$COLS)); do line+=$line; done
+printf "╾" # └
+printf "%s%s%s" "$start" "${line:0:$((COLS/2))}" "$end"
+printf "%s" "$str"
+printf "%s%s%s" "$start" "${line:0:$((COLS/2))}" "$end"
+printf "╼" # ┐
+printf "\n"
diff --git a/bin/kp b/bin/kp
new file mode 100755
index 0000000..f578b04
--- /dev/null
+++ b/bin/kp
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+set -e -o pipefail
+
+KP="$HOME/.local/bin/KeePassXC-2.5.1-x86_64.AppImage"
+[ -e "$KP" ] || {
+    echo "$KP not found" >&2; exit 1
+}
+
+DB="${KP_DB:-$HOME/Dropbox/keepass-box.kdbx}"
+[ -e "$DB" ] || {
+    echo "$DB not found" >&2; exit 1
+}
+
+keepass()
+{
+    "$KP" $@
+}
+
+wofi()
+{
+    wofi -k /dev/null --dmenu --prompt 'Select an entry'
+}
+
+unset passwd
+passwd="$(ASKPASS_PROMPT='Enter KeePassXC password' askpass)"
+[ -n "$passwd" ] || {
+    echo "No password" >&2; exit 1
+}
+
+_kp_command=show
+_kp_opts='-q -a Password'
+_kp_extra_args=
+case $1 in
+    clip)
+        _kp_command='clip'
+        _kp_opts='-q'
+        _kp_extra_args='15' # timeout
+        shift
+        ;;
+    dump)
+        echo "$passwd" | keepass cli export -f csv "$DB"
+        exit 0
+        ;;
+esac
+
+entry="$1"
+[ -n "$entry" ] || {
+    entry=$(echo "$passwd" \
+        | keepass cli ls -q -R "$DB" \
+        | awk '
+{
+    if ($0~/\/$/) { 
+        sub(/^\s+/, "")
+        if ($0!~/^\s+/) {
+            grp = $0
+        } else {
+            print substr($0, match($0, /^\s+/))
+            grp = grp $0
+        }
+            next 
+    } else { 
+        sub(/^\s+/, "")
+        print "/" grp $0 
+    } 
+}' \
+        | grep -v '^/Recycle Bin' \
+        | sort -t/ -k2n \
+        | wofi)
+    [ -n "$entry" ] || {
+        echo "No entry" >&2; exit 1
+    }
+}
+
+echo "$passwd" | keepass cli "$_kp_command" "$_kp_opts" "$DB" "$entry" "$_kp_extra_args"
diff --git a/bin/lock b/bin/lock
new file mode 100755
index 0000000..b147765
--- /dev/null
+++ b/bin/lock
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+set -e
+
+lock_bg=$HOME/.background_lock
+
+blur() {
+    if [ -n "$USE_GRIM" ]; then
+        in=$(mktemp --tmpdir lock-XXX.png)
+        grim -o $(swq current output) ${in}
+    else
+        in=$(readlink -f $HOME/.background)
+    fi
+
+    convert ${in} -filter Gaussian -resize 50% \
+        -define filter:sigma=8.0 -resize 200% ${lock_bg}
+    # convert ${in} -scale 5% -blur 0x2.5 -resize 2000% ${lock_bg}
+    # $GOPATH/bin/stackblur -radius 60 -in $img -out $img
+    # $HOME/devel/go/bin/stackblur -radius 60 -in ${in} -out ${lock_bg} >/dev/null
+}
+
+case "$1" in
+	-h) echo "usage: $0 [gen]"; exit 0 ;;
+	gen) blur; exit 0 ;;
+	*) exec swaylock -f -i ${lock_bg} \
+		--scaling=fill \
+		--font=Iosevka\ R\ Medium \
+		--ignore-empty-password \
+	    	--indicator-caps-lock \
+	    	--show-failed-attempts \
+		--hide-keyboard-layout ;;
+esac
diff --git a/bin/lowbat b/bin/lowbat
new file mode 100755
index 0000000..90702a1
--- /dev/null
+++ b/bin/lowbat
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+
+acpi -b | awk -F'[,:%]' '{print $2, $3}' | {
+	read -r status capacity
+
+	if [[ $status -eq "Discharging" ]]; then
+		if [[ $capacity -lt 10 ]]; then
+			echo "battery capacity: $capacity"
+			notify-send "battery low" "System will hibernate at 5%"
+		fi
+		if [[ $capacity -lt 5 ]]; then
+			echo "hibernating"
+			notify-send "battery low" "System will hibernate now"
+			systemctl hibernate
+		fi
+	fi
+}
diff --git a/bin/mega b/bin/mega
new file mode 100755
index 0000000..b59ebe5
--- /dev/null
+++ b/bin/mega
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# little convenience wrapper around the mega cli tools
+#
+
+usage() {
+    echo "usage: mega -u USER -p PASSWD COMMAND"
+    echo ""
+    exit 0
+}
+
+while getopts ":hu:p:" o; do
+    case $o in
+        u)
+            user=${OPTARG} ;;
+        p)
+            passwd=${OPTARG} ;;
+        h | *)
+            usage ;;
+    esac
+done
+shift $((OPTIND - 1))
+
+[ -z "$1" ] || [ -z "$user" ] || [ -z "$passwd" ] && usage
+if ! command -v ${1} &>/dev/null; then
+    echo "error: ${1} not installed." >&2
+    exit 1
+else
+    cmd=$1
+fi
+shift
+
+exec "mega${cmd}" -u "${user}" -p "${passwd}" $@
diff --git a/bin/menu b/bin/menu
new file mode 100755
index 0000000..b415d1a
--- /dev/null
+++ b/bin/menu
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+arg0=bemenu
+while test $# -gt 0; do
+   case "$1" in
+      -run)
+         arg0=bemenu-run; prompt=bin ;;
+      -p)
+         shift; prompt="$1" ;;
+   esac
+   shift
+done
+
+exec "${arg0}" \
+   -i \
+   --bottom \
+   --no-overlap \
+   --prompt "${prompt}:" \
+   --fn 'Iosevka Sparkle 11' \
+   --nb '#221E1D' --nf '#fffefe' \
+   --tb '#0B346F' --tf '#fffefe' \
+   --fb '#221E1D' --ff '#fffefe' \
+   --sb '#FAD689' \
+   --sf '#221E1D' \
+   --hb '#0B346F' \
+   --hf '#FCFAF2' \
+   --scb '#221E1D' --scf '#fffefe' \
+   "$@"
diff --git a/bin/mesoninit b/bin/mesoninit
new file mode 100755
index 0000000..5ce3b79
--- /dev/null
+++ b/bin/mesoninit
@@ -0,0 +1,191 @@
+#!/bin/bash
+# vim: ft=sh
+
+_meson_dir=`pwd`
+_meson_lang=c
+
+while getopts ":hl:" o; do
+    case $o in
+        l)
+            _meson_lang=$OPTARG
+            ;;
+        h | *)
+            echo "usage: $0 [flags]"
+            echo ""
+            echo "-h :: print help"
+            echo "-l LANG (default: c) :: language to use"
+            echo ""
+            exit 0
+            ;;
+    esac
+done
+shift $((OPTIND - 1))
+
+log() {
+    echo "[*] ${@}" >&2
+}
+
+# allow setting target dir
+if [[ -n $1 ]]; then
+    _meson_dir="$1"
+    [[ ! -d $_meson_dir ]] && mkdir -p $_meson_dir
+    log "creating in $_meson_dir"
+fi
+
+_meson_bin=`basename $_meson_dir`
+_meson_ignore_extra=
+declare -a _meson_default_options
+declare -a _meson_default_files
+
+case $_meson_lang in
+
+    ### C ###
+    c)
+        _meson_default_options=( \
+            'c_std=c11' \
+            'warning_level=2' \
+            'werror=true' \
+        )
+        _meson_default_files=('src/main.c')
+        _meson_ignore_extra='**/*.o'
+        _meson_output="
+x = executable(
+    '$_meson_bin',
+    sources: src
+)"
+
+        log "writing main.c"
+        mkdir $_meson_dir/src
+        cat > $_meson_dir/src/main.c <<EOF
+#define _POSIX_C_SOURCE 200809L
+
+#ifndef VERSION
+#define VERSION "undefined"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc, char **argv) {
+    printf("%s version %s\n\n", *argv, VERSION);
+
+    printf("args (len: %d)", argc-1);
+    for (int i=1; i < argc; ++i) {
+        printf(" '%s'", argv[i]);
+    }
+    printf("\n");
+    return 0;
+}
+EOF
+        ;;
+
+    ### JAVA ###
+    java)
+        _meson_default_options=
+        _meson_default_files=('pkg/Main.class')
+        _meson_ignore_extra='**/*.jar'
+        _meson_output="
+x = jar(
+    '$_meson_bin',
+    main_class: 'pkg.Main',
+    sources: src
+)
+"
+
+        log "writing Main.java"
+        mkdir $_meson_dir/pkg
+        cat > $_meson_dir/pkg/Main.java <<EOF
+package pkg
+
+class Main {
+    public static void main(String[] args) {
+        System.out.println("hello world o/");
+    }
+}
+EOF
+        ;;
+
+
+    *)
+        log "$_meson_lang not supported yet :("
+        exit 1
+        ;;
+esac
+
+meson_array_expand() {
+    for o in "${@}"; do 
+        printf "\t'%s',\n" $o
+    done
+}
+
+log "writing meson.build"
+cat > $_meson_dir/meson.build <<EOF
+project(
+    '$_meson_bin',
+    '$_meson_lang',
+    version: '0.1.0',
+    license: 'MIT',
+    default_options: [
+`meson_array_expand ${_meson_default_options[@]}`
+    ],
+)
+
+version = '"@0@ (" __DATE__ ")"'.format(meson.project_version())
+
+# embed version sourced from git ref
+git = find_program('git', native: true, required: false)
+if git.found()
+    git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags'])
+    git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD'])
+    if git_commit_hash.returncode() == 0 and git_branch.returncode() == 0
+	version = '"@0@ (" __DATE__ ", branch \'@1@\')"'.format(git_commit_hash.stdout().strip(), git_branch.stdout().strip())
+    endif
+endif
+add_project_arguments('-DVERSION=@0@'.format(version), language: 'c')
+
+src = files(
+`meson_array_expand ${_meson_default_files[@]}`
+)
+
+$_meson_output
+
+test('smoke', x)
+EOF
+
+log "intialializing git"
+git init -q $_meson_dir
+
+log "writing .gitignore"
+cat > $_meson_dir/.gitignore <<EOF
+/build
+$_meson_ignore_extra
+EOF
+
+log "writing Makefile"
+cat > $_meson_dir/Makefile <<EOF
+RT ?= podman
+IMAGE ?= docker.io/robertgzr/mesonbuild-$_meson_lang
+
+all: clean compile
+
+build/build.ninja:
+	meson build
+
+build/$_meson_bin$([[ $_meson_lang -eq java ]] && echo '.jar'): compile
+build: build/build.ninja
+	ninja -C build
+
+test: build/$_meson_bin$([[ $_meson_lang -eq java ]] && echo '.jar')
+	ninja -C build test
+
+cbuild:
+        \$(RT) run --rm -it -v \$(shell pwd):/src -w /src \$(IMAGE) make
+
+ctest:
+        \$(RT) run --rm -it -v \$(shell pwd):/src -w /src \$(IMAGE) make test
+
+clean:
+	\$(RM) -r build
+
+.PHONY: clean build test cbuild ctest
+EOF
diff --git a/bin/now b/bin/now
new file mode 100755
index 0000000..7c312b1
--- /dev/null
+++ b/bin/now
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+case "$1" in
+    -here) exec date +%Y-%m-%dT%H:%M:%SZ%:z ;;
+    *)     exec date -u +%Y-%m-%dT%H:%M:%SZ ;;
+esac
diff --git a/bin/obsctl b/bin/obsctl
new file mode 100755
index 0000000..d964f6b
--- /dev/null
+++ b/bin/obsctl
@@ -0,0 +1,121 @@
+#!/bin/env python3
+# -*- coding: utf-8 -*-
+
+import argparse
+import logging
+import sys
+
+from obswebsocket import obsws, requests
+
+
+def __call(ws, req, cb=None):
+    r = ws.call(req)
+    if r.status:
+        logging.debug('Success calling %s' % req)
+        if cb:
+            try:
+                return cb(r)
+            except Exception as e:
+                raise(e)
+    else:
+        raise Exception(r)
+
+
+def listActions(*_):
+    for a in actions:
+        print(a)
+
+
+def startRecording(ws, _):
+    __call(ws, requests.StartRecording())
+
+
+def stopRecording(ws, _):
+    __call(ws, requests.StopRecording())
+    __call(ws, requests.GetRecordingFolder(),
+           lambda r: print('Saved recording at: %s' % r.getRecFolder()))
+
+
+def startStreaming(ws, _):
+    __call(ws, requests.StartStreaming())
+
+
+def stopStreaming(ws, _):
+    __call(ws, requests.StopStreaming())
+
+
+def listScenes(ws, _):
+    def cb(r):
+        current_scene = r.getCurrentScene()
+        for scene in r.getScenes():
+            scene = scene['name']
+            print('%s %s' % (('*' if current_scene == scene else ' '), scene))
+
+    __call(ws, requests.GetSceneList(), cb)
+
+
+def switchScene(ws, args):
+    def cb(r):
+        return list(map(lambda s: s['name'], r.getScenes()))
+
+    scenes = __call(ws, requests.GetSceneList(), cb)
+
+    if len(args) != 1:
+        raise Exception('need exactly 1 argument')
+
+    if args[0] in scenes:
+        __call(ws, requests.SetCurrentScene(args[0]))
+
+
+sys.path.append('../')
+
+actions = {
+    'list-actions': listActions,
+    'start-recording': startRecording,
+    'stop-recording': stopRecording,
+    'start-streaming': startStreaming,
+    'stop-streaming': stopStreaming,
+    'list-scenes': listScenes,
+    'switch-scene': switchScene,
+}
+
+if __name__ == '__main__':
+    try:
+        app = argparse.ArgumentParser(
+            description=__doc__,
+            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+        app.add_argument('-d', '--debug',
+                         action='store_true',
+                         help='show debug logs')
+        app.add_argument('-H', '--host', type=str,
+                         default='localhost',
+                         help='obs-websocket host')
+        app.add_argument('-P', '--port', type=int,
+                         default=4444,
+                         help='obs-websocket port')
+        app.add_argument('-p', '--password', type=str,
+                         help='obs-websocket password')
+        app.add_argument('action', type=str,
+                         choices=actions.keys(),
+                         help='the command to run')
+        app.add_argument('args',
+                         nargs=argparse.REMAINDER,
+                         help='optional action argument')
+        args = app.parse_args()
+
+        if args.debug:
+            logging.basicConfig(level=logging.DEBUG)
+
+        ws = obsws(args.host, args.port, args.password)
+        ws.connect()
+
+        actions[args.action](ws, args.args)
+
+        ws.disconnect()
+        exit(0)
+
+    except KeyboardInterrupt:
+        pass
+    except Exception as e:
+        logging.error(e)
diff --git a/bin/open b/bin/open
new file mode 100755
index 0000000..9a1eb3f
--- /dev/null
+++ b/bin/open
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+set -m
+termmode=0
+
+while getopts ":hg" opt; do
+    case $opt in
+        g)
+            termmode=1
+            ;;
+        h|*)
+            echo "usage: open [-t] FILE"
+            echo ""
+            echo "  -t  Use only Terminal-based applications"
+            exit 2
+            ;;
+    esac
+done
+shift $((OPTIND-1))
+
+in="$1"
+
+mime=$(file --mime-type $in | awk -F': ' '{print $2}' )
+ext=$(basename $in | rev | cut -d'.' -f1 | rev)
+opener=xdg-open
+
+fork() {
+    exec $@ 1>&2 2>/dev/null &
+}
+
+term() {
+    fork alacritty --title="$1" --command "$@"
+}
+
+case $mime in
+    inode/directory)    [ $termmode -eq 0 ] && vifm "$in" || fork nautilus -w "$in" ;;
+    # inode/file)         fork nautilus -w $in ;;
+
+    image/*)            fork sxiv "$in" ;;
+    video/*)            fork mpv --force-widow=yes "$in" ;;
+    audio/*)            fork mpv --force-widow=yes "$in" ;;
+
+    application/pdf)    fork zathura "$in" ;;
+    application/vnd.ms-opentype|application/font-sfnt)
+                        fork gnome-font-viewer "$in" ;;
+
+    # now text...
+    text/*)
+        # echo $mime $ext
+        case $ext in
+            md|markdown) fork shiba "$(readlink -f "$in")" ;;
+            desktop) fork gtk-launch "$(basename "$in" | cut -d'.' -f1)" ;;
+            *)           fork term nvim "$in" ;;
+        esac
+        ;;
+
+    # everything else...
+    *) exec $opener "$in" ;;
+esac
diff --git a/bin/preview b/bin/preview
new file mode 100755
index 0000000..41ebfb9
--- /dev/null
+++ b/bin/preview
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+REVERSE="\x1b[7m"
+RESET="\x1b[m"
+
+if [ -z "$1" ]; then
+  echo "usage: $0 FILENAME[:LINENO][:IGNORED]"
+  exit 1
+fi
+
+IFS=':' read -r -a INPUT <<< "$1"
+FILE=${INPUT[0]}
+CENTER=${INPUT[1]}
+
+if [[ $1 =~ ^[A-Z]:\\ ]]; then
+  FILE=$FILE:${INPUT[1]}
+  CENTER=${INPUT[2]}
+fi
+
+if [ ! -r "$FILE" ]; then
+  echo "File not found ${FILE}"
+  exit 1
+fi
+
+if [[ "$(file --dereference --mime "$FILE")" =~ binary ]]; then
+  echo "$1 is a binary file"
+  exit 0
+fi
+
+if [ -z "$CENTER" ]; then
+  CENTER=0
+fi
+
+if [ -z "$LINES" ]; then
+  if [ -r /dev/tty ]; then
+    LINES=$(stty size < /dev/tty | awk '{print $1}')
+  else
+    LINES=40
+  fi
+fi
+
+FIRST=$(($CENTER-$LINES/3))
+FIRST=$(($FIRST < 1 ? 1 : $FIRST))
+LAST=$((${FIRST}+${LINES}-1))
+
+DEFAULT_COMMAND="bat --style=numbers --color=always {} || highlight -O ansi -l {} || coderay {} || rougify {} || cat {}"
+CMD=${FZF_PREVIEW_COMMAND:-$DEFAULT_COMMAND}
+CMD=${CMD//{\}/$(printf %q "$FILE")}
+
+eval "$CMD" 2> /dev/null | awk "NR >= $FIRST && NR <= $LAST { \
+    if (NR == $CENTER) \
+        { gsub(/\x1b[[0-9;]*m/, \"&$REVERSE\"); printf(\"$REVERSE%s\n$RESET\", \$0); } \
+    else printf(\"$RESET%s\n\", \$0); \
+    }"
diff --git a/bin/qemuu b/bin/qemuu
new file mode 100755
index 0000000..3e4548c
--- /dev/null
+++ b/bin/qemuu
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+Q_ARCH='x86_64'
+Q_NAME='qemu'
+Q_SSH_PORT='2222'
+
+USAGE="usage: `basename $0` [options] [--] [qemu options...]
+Options:
+    -A ARCH     VM architecture
+    -p PORT     The port on localhost to map to the VM's sshd. [2222]
+    -d FILE     Set disk
+    -n NAME     Set name
+    -h          This ;-)
+
+A simple wrapper around qemu for running balenaOS images.
+See the qemu(1) man page for more details.
+"
+
+Q_ARGS=""
+
+while getopts ":hvd:A:n:" o; do
+    case "$o" in
+        A)
+            Q_ARCH="$OPTARG"
+            die "Found \"-$o $Q_ARCH\", not supported yet!" ;;
+        d)
+            Q_DISK="$OPTARG"
+            Q_ARGS="${Q_ARGS} -drive file="${Q_DISK}",media=disk,cache=none,format=raw" ;;
+        p)
+            Q_SSH_PORT="$OPTARG" ;;
+        n)
+            Q_NAME="$OPTARG" ;;
+        v)
+            set -x ;;
+        h|*)
+            echo "$USAGE"
+            exit ;;
+    esac
+done
+shift $(($OPTIND-1))
+
+case "${Q_ARCH}" in
+    x86_64|amd64)
+        qemu-system-x86_64 \
+            -name "$Q_NAME" \
+            -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \
+            -net nic,model=virtio -net user \
+            -nic user,hostfwd=tcp::${Q_SSH_PORT}-:22222 \
+            -machine type=pc,accel=kvm -smp 4 -cpu host \
+            ${Q_ARGS} \
+            "$@"
+        ;;
+    *) die "Unsupported arch" ;;
+esac
+
+exit $?
diff --git a/bin/randgen b/bin/randgen
new file mode 100755
index 0000000..77bca83
--- /dev/null
+++ b/bin/randgen
@@ -0,0 +1,8 @@
+#!/bin/zsh
+
+N=${1:-10}
+
+for d in {0..$N}; do
+    printf '%s' ${RANDOM:0:1}
+done
+printf '\n'
diff --git a/bin/semver b/bin/semver
new file mode 100755
index 0000000..e2b51e7
--- /dev/null
+++ b/bin/semver
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+# src:
+# https://git.sr.ht/~sircmpwn/dotfiles/blob/f9e7e88a72c2b87e5ab823205cb3a3aa8f3babd0/bin/semver
+# usage: semver [patch|minor|major|<new_version>]
+# desc: will tag a new version either based on last tag or <new_version>
+#
+# patch(robertgzr)
+# allow simply tagging a new non-annotated release
+
+import os
+import subprocess
+import sys
+import tempfile
+
+if subprocess.run(["git", "branch", "--show-current"],
+                  stdout=subprocess.PIPE).stdout.decode().strip() != "master":
+    print("WARNING! Not on the master branch.")
+
+subprocess.run(["git", "pull", "--rebase"])
+
+if sys.argv[1] == "-t":
+    sys.argv = [sys.argv[0]] + sys.argv[2:]
+    simple_tag = True
+    describe_args = "--tag"
+else:
+    describe_args = "--abbrev=0"
+
+p = subprocess.run(["git", "describe", describe_args], stdout=subprocess.PIPE)
+describe = p.stdout.decode().strip()
+old_version = describe.split("-")[0].split(".")
+if len(old_version) == 2:
+    [major, minor] = old_version
+    [major, minor] = map(int, [major, minor])
+    patch = 0
+else:
+    [major, minor, patch] = old_version
+    [major, minor, patch] = map(int, [major, minor, patch])
+
+p = subprocess.run(["git", "shortlog", "--no-merges", f"{describe}..HEAD"],
+                   stdout=subprocess.PIPE)
+shortlog = p.stdout.decode()
+
+new_version = None
+
+if sys.argv[1] == "patch":
+    patch += 1
+elif sys.argv[1] == "minor":
+    minor += 1
+    patch = 0
+elif sys.argv[1] == "major":
+    major += 1
+    minor = patch = 0
+else:
+    new_version = sys.argv[1]
+
+if new_version is None:
+    if len(old_version) == 2 and patch == 0:
+        new_version = f"{major}.{minor}"
+    else:
+        new_version = f"{major}.{minor}.{patch}"
+
+p = None
+if os.path.exists("contrib/_incr_version"):
+    p = subprocess.run(["contrib/_incr_version", describe, new_version])
+elif os.path.exists(".git/_incr_version"):
+    print("Warning: _incr_version found at legacy path")
+    p = subprocess.run([".git/_incr_version", describe, new_version])
+else:
+    print("Warning: no _incr_version script. " +
+          "Does this project have any specific release requirements?")
+
+if p and p.returncode != 0:
+    print("Error: _incr_version returned nonzero exit code")
+    sys.exit(1)
+
+if simple_tag:
+    subprocess.run(["git", "tag", new_version])
+
+else:
+    with tempfile.NamedTemporaryFile() as f:
+        basename = os.path.basename(os.getcwd())
+        f.write(f"{basename} {new_version}\n\n".encode())
+        f.write(shortlog.encode())
+        f.flush()
+        subprocess.run(["git", "tag", "-e", "-F", f.name, "-a", new_version])
+
+print(new_version)
diff --git a/bin/soundswitch_dmenu b/bin/soundswitch_dmenu
new file mode 100755
index 0000000..f4a6338
--- /dev/null
+++ b/bin/soundswitch_dmenu
@@ -0,0 +1,133 @@
+#!/bin/sh
+# 
+# This script changes the pulseaudio sink for the currently focused application 
+#
+# If it is run with $1 = global the default sink will be switched instead
+
+set -eo pipefail
+
+rofi() {
+    # exec rofi -dmenu -p pactl -l 5 -i "$@"
+    exec wofi --dmenu -k /dev/null --prompt pactl "$@"
+}
+bail() {
+    exec echo "$@" | wofi -p pactrl-error
+    exit 1
+}
+
+list_sinks() {
+    pacmd list-sinks | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?name: <([^\r]+)>.+?device.description = "([^\r]+)"\r.+?(?=index:|$)/\1: \3 \t<\2>\r/g' | tr '\r' '\n' | tail -n +2
+}
+parse_sink() {
+    perl -pe 's/.*<([^\r]+)>/\1/g'
+}
+
+# usage: move_sink_inputs_to <target-sink> <sink-input-ids>
+move_sink_inputs_to() {
+    target_sink_id="${1}"
+    shift
+    for sink_input_id in "${@}"; do
+        # notify-send "pactl-debug" "pacmd move-sink-input ${sink_input_id} ${target_sink_id}"
+        pacmd move-sink-input ${sink_input_id} ${target_sink_id}
+    done
+}
+
+list_active_sink_input_ids() {
+    pacmd list-sink-inputs | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?application\.process\.binary = "([^\r]+)"\r.+?(?=index:|$)/\2: \1\r/g' | tr '\r' '\n' | tail -n +2
+}
+parse_active_sink_input_id() {
+    perl -pe 's/.*:([^\r]+)/\1/g'
+}
+
+list_cards() {
+    pacmd list-cards | tr '\n' '\r' | perl -pe 's/[^\r]*index: ([0-9]+).+?device.description = \"([^\r]+)\".+?active.profile: <([^\r]+)>.+?(?=index|$)/\1: \2\tProfile: <\3>\r/g' | tr '\r' '\n' | tail -n +2
+}
+
+list_card_profiles() {
+    idx="${1}"
+    pacmd list-cards | tr '\n' '\r' | perl -pe "s/.+index: $idx\\r.+?[^\\r]*profiles:\\r(.+?)[^\\r]*active.+?$/\\1/g;" -e 's/\t\t//g;' -e 's/\([^\r]+\)\r/\r/g;' -e 's/([^\r]+):[\s]+([^\r]+)[\s]+(\r|$)/\2  <\1>\r/g;' | tr '\r' '\n' | tail -n +2
+}
+parse_profile() {
+    perl -pe 's/^.+<(.+)>$/\1/g'
+}
+
+get_focused_command() {
+    focused_pid=`swaymsg -t get_tree | jq '.. | select(.focused? == true) | .pid'`
+    ps -o comm= $focused_pid
+}
+
+# switch default global sink
+_default() {
+    target_sink=`echo "${sel}" | parse_sink`
+    pacmd set-default-sink $target_sink
+    notify-send --expire-time=2000 "New default sink" "<b>${target_sink}</b>"
+    move_sink_inputs_to $target_sink `pactl list sink-inputs short | cut -f 1 | tr '\n' ' '`
+}
+
+# switch output sink of focused application
+_focused() {
+    focused_app=`get_focused_command`
+    focused_app_input_sink_id=`list_active_sink_input_ids | grep "${focused_app}" | parse_active_sink_input_id`
+    target_sink=`list_sinks | rofi | parse_sink`
+
+    notify-send --expire-time=2000 "Switch sink" "Switch <b>${focused_app}</b> to <i>${target_sink}</i>"
+    move_sink_inputs_to "${target_sink}" "${focused_app_input_sink_id}"
+}
+
+# switch output sink of application
+_app() {
+    ret=`list_active_sink_input_ids | rofi`
+    focused_app_input_sink_id=`echo $ret | parse_active_sink_input_id`
+    focused_app=`echo $ret | perl -pe 's/^(.+):.+$/\1/g'`
+
+    target_sink=`list_sinks | rofi | parse_sink`
+
+    notify-send --expire-time=2000 "Switch sink" "Switch <b>${focused_app}</b> to <i>${target_sink}</i>"
+    move_sink_inputs_to "${target_sink}" "${focused_app_input_sink_id}"
+}
+
+_switch_profile() {
+    card=`list_cards | rofi`
+    card_id=`echo $card | perl -pe 's/([0-9]+):.+$/\1/g'`
+    card_name=`echo $card | perl -pe 's/^.+: (.+?)[\s]+Profile.+$/\1/g'`
+    new_profile=`list_card_profiles $card_id | rofi | parse_profile`
+
+    notify-send -t 2000 "Change card profile" "Switch <b>$card_name</b> to <i>$new_profile</i>"
+    # notify-send "pactl-debug" "pacmd set-card-profile $card_id $new_profile"
+    pacmd set-card-profile $card_id $new_profile
+}
+
+ACTIONS=(
+    "Switch profile"
+    "Change app output sink"
+)
+list_actions() {
+    for a in "${ACTIONS[@]}"; do echo "$a"; done
+}
+
+_global() {
+    sel=`{ list_sinks; echo ; list_actions; } | rofi`
+    if [[ -z $sel ]]; then
+        return
+    fi
+    case "${sel}" in
+        ${ACTIONS[0]}) 
+            _switch_profile 
+            ;;
+        ${ACTIONS[1]}) 
+            _app
+            ;;
+        *)
+            _default
+            ;;
+    esac
+}
+
+case "${1}" in
+    global)
+        _global
+        ;;
+    *)
+        _focused
+        ;;
+esac
diff --git a/bin/streamon b/bin/streamon
new file mode 100755
index 0000000..a652b77
--- /dev/null
+++ b/bin/streamon
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -ex
+
+sway exec "wshowkeys -a bottom -a left -F 'Iosevka R 26' -t 1"
+
+obsctl start-streaming
diff --git a/bin/sway-run b/bin/sway-run
new file mode 100755
index 0000000..be60ae4
--- /dev/null
+++ b/bin/sway-run
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+set -ex
+
+if [ "$XDG_RUNTIME_DIR" == "" ]; then
+    export XDG_RUNTIME_DIR=/tmp
+fi
+
+LOCKED=0
+SWAY_BIN=/usr/bin/sway
+SWAY_ARGS=
+
+while getopts ":dcDL" o; do
+    case $o in
+        d) SWAY_ARGS="$SWAY_ARGS --debug" ;;
+        D)
+            export XDG_RUNTIME_DIR=/tmp/sway-run-debug
+            [ ! -d "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR"
+            ;;
+        c) SWAY_ARGS="$SWAY_ARGS --config $XDG_CONFIG_HOME/sway/simple.conf" ;;
+        L) LOCKED=1 ;;
+        *) echo "unknown flag: $o" 1>&2
+    esac
+done
+shift $((OPTIND-1))
+
+if [ $LOCKED -eq 1 ]; then
+    sh -c 'sleep 1; $HOME/.dotfiles/bin/lock' & disown
+    exec "$0" $@
+fi
+
+case $1 in
+    locked)     shift; exec $0 -L ;;
+    git)        shift; 
+        SWAY_SRC="$HOME/devel/upstream/github.com/swaywm/sway"
+        SWAY_BIN="$SWAY_SRC/build/sway/sway";
+        export PATH="$SWAY_SRC/build/sway:$PATH"
+        export LD_LIBRARY_PATH="$SWAY_SRC/build/subprojects/wlroots:$LD_LIBRARY_PATH"
+        ;;
+    system)     shift; exec $0 ;;
+    clean)      shift; exec $0 -c ;;
+esac
+
+TS="$(date -u --rfc-3339=seconds | tr ' ' 'T')"
+LOG=/var/log/sway/"$TS".log
+
+exec $SWAY_BIN -Ddamage=rerender $SWAY_ARGS $@ 1>&2 2>"$LOG"
diff --git a/bin/swq b/bin/swq
new file mode 100755
index 0000000..0336f64
--- /dev/null
+++ b/bin/swq
@@ -0,0 +1,139 @@
+#!/usr/bin/env bash
+
+swtree()
+{
+    swaymsg -t get_tree
+}
+
+has_appid()
+{
+    swtree | jq "[ .. | select(.app_id?==\"$1\") // select(.window_properties?.class==\"$1\") // select(.window_properties?.instance==\"$1\") ]"
+}
+
+has_pid()
+{
+    q=''
+    IFS=$' '; for pid in $1; do
+        if [[ -n $q ]]; then
+            q=".pid?==$pid or $q"
+        else
+            q=".pid?==$pid"
+        fi
+    done
+    swtree | jq "[ .. | select($q) ]"
+}
+
+has_exe()
+{
+    pid_of_win="$(pgrep -x "$1" -d' ')"
+    if [[ -z "$pid_of_win" ]]; then 
+        echo "process not found" >&2
+        exit 1
+    fi
+
+    ppid_of_proc="$(ps -q "$(echo "$pid_of_win" | tr ' ' ',')" -o ppid= |tr -d ' '| tr '\n' ','| sed 's/,$//')"
+    if [[ -z "$ppid_of_proc" ]]; then
+        echo "parent process not found" >&2
+    fi
+    IFS=$'\n'; for comm in $(ps -q "$ppid_of_proc" -o pid=,comm=); do
+        if [[ "$comm"  =~ ^.*sh$ ]]; then
+            # parent process is a shell
+            # need to get parent process of shell (the terminal emulator)
+            pid_of_win="$pid_of_win $(ps -p "$(echo "$comm" | awk '{print $1}')" -o ppid= | tr -d ' ')"
+        fi
+    done
+
+    if [[ -z "$pid_of_win" ]]; then 
+        echo "process not found" >&2
+        exit 1
+    fi
+    has_pid "$pid_of_win"
+}
+
+latest()
+{
+    type="$1"
+    swtree | jq "[ .. | select(.type?==\"$type\") ] | max_by(.id)"
+}
+
+list()
+{
+    swtree | jq "[ .. | select(.type?==\"$1\") | .nodes=[\"...\"] ]"
+}
+
+moveto()
+{
+    if [ ! -t 0 ]; then
+        read -r -i -e conId
+        sway [con_id="$conId"] move to workspace "$1" 
+    else
+        sway [app_id="$1"] move to workspace "$2" 
+    fi
+}
+
+# meta commands
+
+_has()
+{
+    case "$1" in
+        appid) has_appid "$2" ;;
+        pid) has_pid "$2";;
+        exe) has_exe "$2";;
+        *)
+            printf 'usage: swq has COMMAND <args>\n'
+            printf '\nCOMMANDS:\n'
+            printf ' appid: returns the json of the window filtered by app_id/class\n'
+            printf ' pid:   returns the json of the window filtered by pid\n'
+            printf ' exe:   returns the json of the window filtered by process name\n'
+            printf '\n'
+    esac
+}
+_latest()
+{
+    [ "$1" == "ws" ] && 1="workspace"
+    latest "$1"
+}
+_list()
+{
+    [ -n "$1" ] || { echo "usage: swq list <con|workspace>"; exit 1; }
+    [ "$1" == "ws" ] && _list workspace
+    list "$1"
+}
+_current()
+{
+    case "$1" in
+        window) _current con ;;
+        con) swtree | jq '.. | select(.type?=="con" and .focused?==true)' ;;
+
+        ws) _current workspace ;;
+        workspace) swtree | jq '.. | select(.type?=="workspace") | . as $parent | .nodes[]? | if .focused then $parent else empty end' ;;
+        output) $0 current workspace | jq -r '.output' ;;
+        *)
+            printf 'usage: swq current <con|workspace|output>\n\n' ;;
+    esac
+}
+
+case "$1" in
+    has) shift; _has $@ ;;
+    latest) shift; _latest $@ ;;
+    list|ls) shift; _list $@ ;;
+    current|cur) shift; _current $@ ;;
+
+    focus-latest-con) sway [con_id="$(latest con | jq -r '.id')"] focus ;;
+    moveto) shift; moveto $@ ;;
+
+    *)
+        printf 'usage: swq COMMAND\n'
+        printf '\n'
+        printf 'QUERY COMMANDS:\n'
+        printf ' has:     get object by criteria\n'
+        printf ' latest:  get newest objects\n'
+        printf ' list:    list objects\n'
+        printf ' current: get focused objects\n'
+
+        printf '\n'
+        printf 'ACTION COMMANDS:\n'
+        printf ' focus-latest-con\n'
+        printf ' moveto <appid> <ws>:  move container with <appid> to <ws>\n'
+        printf '\n'
+esac
diff --git a/bin/termify b/bin/termify
new file mode 100755
index 0000000..4778470
--- /dev/null
+++ b/bin/termify
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+arg0=$(basename $1 || echo $1)
+
+case "$TERM" in
+	alacritty) args="--title='$arg0' --class='$arg0' --command" ;;
+	*) args="" ;;
+esac
+
+exec $TERM $args $@ &
diff --git a/bin/todo b/bin/todo
new file mode 100755
index 0000000..1fe3c62
--- /dev/null
+++ b/bin/todo
@@ -0,0 +1,128 @@
+#!/bin/bash
+
+_todo_files=(".todo.md" ".org.md", "TODO")
+_todo_file=""
+
+has_todo_file() {
+    cwd=`pwd`
+    for f in ${_todo_files[@]}; do
+      [[ -f "$cwd/$f" ]] && _todo_file="$cwd/$f" && return 0
+    done
+    return 1
+}
+
+print_org() {
+    awk '
+function filename() {
+    n = split(FILENAME, a, "/")
+    return a[n]
+}
+function res() {
+  return "\033[0;10m"
+}
+function bold(s) {
+    return "" s res()
+}
+function red(s) {
+    "tput setaf 1" | getline redc;
+    return redc s res()
+}
+function green(s) {
+    "tput setaf 2" | getline greenc;
+    return greenc s res()
+}
+function blue(s) {
+    "tput setaf 5" | getline bluec;
+    return bluec s res()
+}
+function magenta(s) {
+    "tput setaf 191" | getline magc;
+    return magc s res()
+  }
+{
+    if ($1 ~ /^#+/) 
+        block=$0
+
+    if ($1 ~ /^.*(-|*)/) {
+        // support TODO files
+        if (filename() == "TODO") {
+            $0 = gensub(/(^[\w]*(-|*))/, "\\1 TODO", "1", $0)
+        }
+
+        match($0, /\s(TODO|WIP|WAITING|DONE|\?\?\?)\s/, arr)
+        if (arr[1] != "") {
+            tasks[block][arr[1]][i++]=$0
+        }
+    }
+}
+END { 
+    for (b in tasks) {
+        print b
+        for (status in tasks[b]) {
+            for (t in tasks[b][status]) {
+                txt = tasks[b][status][t]
+                txt = gensub(/(TODO)/, red("\\1"), "1", txt)
+                txt = gensub(/(WIP)/, blue("\\1"), "1", txt)
+                txt = gensub(/(DONE)/, green("\\1"), "1", txt)
+                txt = gensub(/(\?\?\?)/, bold(magenta("\\1")), "1", txt)
+                print txt
+            }
+        }
+        printf "\n"
+    }
+}' $1
+}
+
+print_shell_hook() {
+    case $1 in
+        zsh)
+            echo "_orgdown_hook() {$0;}
+typeset -ag precmd_functions;
+if [[ -z \${precmd_functions[(r)_orgdown_hook]} ]]; then
+  precmd_functions+=_orgdown_hook;
+fi"
+            ;;
+        cd)
+            echo "cd() { builtin cd \"\$@\" && $0; }"
+            ;;
+        -h|--help|*)
+            echo "usage: todo hook <integration>"
+            echo ""
+            echo "supported integrations:"
+            echo " - zsh: run todo as a precmd hook"
+            echo "   (this can get quite annoying when you're in a direcory that has a todo file)"
+            echo " - cd: run todo as a post-cd hook"
+            echo ""
+            exit 1
+            ;;
+    esac
+}
+
+case "$1" in
+    hook)
+        shift
+        print_shell_hook "$1"
+        ;;
+    -h|--help)
+        echo "usage: todo [-r] [hook <integration>]"
+        echo "A quite stupid todo/agenda script that reminds you of things you need to do"
+        echo ""
+        echo " -r, --recursive    walk the file tree, searching for todos"
+        echo " <integration>      shell integration, you should eval this"
+        echo ""
+        exit 0
+        ;;
+    -r|--recursive|-R)
+        while read FILE; do
+          hr "todo [$FILE]"
+          print_org $FILE;
+        done < <(rg --files ${_todo_files[@]/#/-g })
+        ;;
+    *)
+        [ -f "$1" ] && { print_org $1; exit 0; }
+        has_todo_file && {
+          hr "todo"
+          print_org $_todo_file
+        }
+        ;;
+esac
diff --git a/bin/trash b/bin/trash
new file mode 100755
index 0000000..5b810c1
--- /dev/null
+++ b/bin/trash
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+set -e
+
+TRASH="$HOME/.trash"
+if [ ! -d "$TRASH" ]; then
+    mkdir -p "$TRASH"
+fi
+
+empty_trash() {
+    if [ ! -d "$TRASH" ] || [ -z "$(ls "$TRASH")" ]; then
+        exit 0
+    fi
+    echo " +++ trash +++"
+    ls "$TRASH"
+    printf " +++ take it out? [yN] "; read -r yn
+    if [ "$yn" = "y" ]; then
+        printf ' +++ removing %s/* ' "$TRASH"
+        rm -rf "${TRASH:?}/*"
+        echo 'done.'
+    fi
+}
+
+install_auto_empty() {
+    case "$SHELL" in
+        *zsh) logoutfile="$ZDOTDIR/.zlogout" ;;
+        *bash) logoutfile="$HOME/.bash_logout" ;;
+        *)
+            echo "unknown shell $SHELL" 1>&2; exit 1 ;;
+    esac
+    cat > "$logoutfile" <<EOF
+# auto-empty-trash routine of $0
+yes | $0 --empty
+EOF
+}
+
+case "$1" in
+    -e|--empty)
+        empty_trash
+        exit 0 ;;
+    -i|--install)
+        install_auto_empty
+        exit 0 ;;
+    -h|--help)
+        echo "usage: trash [-e|--empty] FILE..."
+        exit 1 ;;
+esac
+
+# main
+for f in "$@"; do
+    echo "+ $(basename "$f")"
+    mv "$f" "$TRASH/"
+done
diff --git a/bin/url-launcher b/bin/url-launcher
new file mode 100755
index 0000000..4bb682e
--- /dev/null
+++ b/bin/url-launcher
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -e
+wl-copy "$@"
+notify-send --urgency=low "copied url" "$@"
diff --git a/bin/usb2sys b/bin/usb2sys
new file mode 100755
index 0000000..d810431
--- /dev/null
+++ b/bin/usb2sys
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# usb2sys - find lsusb device in /sys file system
+#
+# https://superuser.com/a/1253083
+#
+
+die()
+{
+    echo "$@"
+    exit 1
+}
+
+[[ $# -lt 1 ]] && die "need vendor and product ids (from lsusb) as dddd:dddd"
+
+vendor=${1%:*}
+product=${1##*:}
+
+sys=/sys/bus/usb/devices/
+cd $sys
+
+for d in *; do
+    path=$sys/$d
+    if [ -f $path/idProduct ]; then
+      prod=$( cat $path/idProduct )
+      vend=$( cat $path/idVendor )
+
+      if [ $prod = $product -a $vend = $vendor ]; then
+        echo prod = $prod
+        echo vend = $vend
+        echo /sys device is $path
+        cat $path/power/wakeup
+        echo ""
+      fi
+    fi
+done
diff --git a/bin/wsudo b/bin/wsudo
new file mode 100755
index 0000000..1ac1e33
--- /dev/null
+++ b/bin/wsudo
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+#
+# Enable root access to x-windows system.
+#
+# Motivation: Trying to run a graphical application as root via su, sudo in a 
+# Wayland session (e.g. GParted or Gedit), will fail. Apps which use polkit to
+# request administrator permissions for just certain operations and only when 
+# needed are not affected (they are not started as root right away). 
+# [1] https://bugzilla.redhat.com/show_bug.cgi?id=1274451
+#
+# Based on a Reddit comment.
+# [2] https://www.reddit.com/r/Fedora/comments/5eb633/solution_running_graphical_app_with_sudo_in/
+
+usage() {
+    printf "usage: wsudo [-d] [command]\n"
+}
+
+# make sure there is an argument
+if (( $# == 0 )); then
+    echo "error: Illegal number of parameters." >&2
+    exit 1
+fi
+
+if ! type -p "$1" &>/dev/null; then
+    echo "$1 not found. Aborting." >&2
+    exit 1
+fi
+
+for cmd in sudo xhost; do
+    if ! type -P $cmd &>/dev/null; then
+        echo "$cmd it's not installed. Aborting." >&2
+        exit 1
+    fi
+done
+
+set -x
+
+xhost +SI:localuser:root
+SUDO_ASKPASS="`command -v askpass`" sudo -A "${@}"
+#disable root access after application terminates
+xhost -SI:localuser:root
+#print access status to allow verification that root access was removed
+xhost