diff options
| author | Robert Günzler <r@gnzler.io> | 2020-01-26 17:16:07 +0100 |
|---|---|---|
| committer | Robert Günzler <r@gnzler.io> | 2020-01-26 18:00:59 +0100 |
| commit | f5d56602df70950de6b7d40966b00c9939c81763 (patch) | |
| tree | b50ffeb5496fe3525cc1bab2629389ced95435b7 /bin | |
Intial commit of v2
See https://drewdevault.com/2019/12/30/dotfiles.html
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/askpass | 8 | ||||
| -rwxr-xr-x | bin/chrome-extension-dl | 83 | ||||
| -rwxr-xr-x | bin/clone | 127 | ||||
| -rwxr-xr-x | bin/colorpick | 6 | ||||
| -rwxr-xr-x | bin/find-rust-target-dir | 16 | ||||
| -rwxr-xr-x | bin/gh | 114 | ||||
| -rwxr-xr-x | bin/grimshot | 146 | ||||
| -rwxr-xr-x | bin/hibiki/mount-solus-backup | 35 | ||||
| -rwxr-xr-x | bin/hibiki/soundswitch | 41 | ||||
| -rwxr-xr-x | bin/hr | 20 | ||||
| -rwxr-xr-x | bin/kp | 75 | ||||
| -rwxr-xr-x | bin/lock | 32 | ||||
| -rwxr-xr-x | bin/lowbat | 19 | ||||
| -rwxr-xr-x | bin/mega | 33 | ||||
| -rwxr-xr-x | bin/menu | 28 | ||||
| -rwxr-xr-x | bin/mesoninit | 191 | ||||
| -rwxr-xr-x | bin/now | 6 | ||||
| -rwxr-xr-x | bin/obsctl | 121 | ||||
| -rwxr-xr-x | bin/open | 59 | ||||
| -rwxr-xr-x | bin/preview | 54 | ||||
| -rwxr-xr-x | bin/qemuu | 56 | ||||
| -rwxr-xr-x | bin/randgen | 8 | ||||
| -rwxr-xr-x | bin/semver | 88 | ||||
| -rwxr-xr-x | bin/soundswitch_dmenu | 133 | ||||
| -rwxr-xr-x | bin/streamon | 7 | ||||
| -rwxr-xr-x | bin/sway-run | 47 | ||||
| -rwxr-xr-x | bin/swq | 139 | ||||
| -rwxr-xr-x | bin/termify | 10 | ||||
| -rwxr-xr-x | bin/todo | 128 | ||||
| -rwxr-xr-x | bin/trash | 53 | ||||
| -rwxr-xr-x | bin/url-launcher | 5 | ||||
| -rwxr-xr-x | bin/usb2sys | 36 | ||||
| -rwxr-xr-x | bin/wsudo | 44 |
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 |