scripts

Utilitity scripts
git clone git://gtms.dev/scripts.git
Log | Files | Refs

commit 5c62dfc64f48426196f024a004cd566da310bdc2
Author: Tomas Nemec <nemi@skaut.cz>
Date:   Fri,  6 Aug 2021 15:13:11 +0200

init

Diffstat:
Aaudio | 7+++++++
Abar-update | 3+++
Abmadd | 21+++++++++++++++++++++
Abmls | 35+++++++++++++++++++++++++++++++++++
Abmmenu | 45+++++++++++++++++++++++++++++++++++++++++++++
Abmrm | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abspswallow | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Achcol | 30++++++++++++++++++++++++++++++
Admenu_pass | 17+++++++++++++++++
Admenu_path | 13+++++++++++++
Admenu_run | 2++
Adpgwdev | 38++++++++++++++++++++++++++++++++++++++
Adpgwr | 37+++++++++++++++++++++++++++++++++++++
Adunst-reload | 4++++
Aexternal_rules | 12++++++++++++
Akillclass.sh | 12++++++++++++
Amail_check.lua | 43+++++++++++++++++++++++++++++++++++++++++++
Amail_count.lua | 23+++++++++++++++++++++++
Amdev | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anewmails.sh | 18++++++++++++++++++
Anewrss | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anoswallow_dmenu | 17+++++++++++++++++
Anoswallow_open | 7+++++++
Aopenhtml | 8++++++++
Apass-section | 48++++++++++++++++++++++++++++++++++++++++++++++++
Apassmenu | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apassmenu_section | 19+++++++++++++++++++
Aplayerctl-custom.sh | 5+++++
Apulseaudio-control | 518+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascratchpad_hide.sh | 12++++++++++++
Ascratchpad_show.sh | 12++++++++++++
Ascratchpad_toggle.sh | 12++++++++++++
Asfc | 7+++++++
Asfcut | 20++++++++++++++++++++
Asfd | 5+++++
Asfeedmr | 4++++
Asfopen | 15+++++++++++++++
Asshmenu | 10++++++++++
Atwitch-notifier.go | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
39 files changed, 1731 insertions(+), 0 deletions(-)

diff --git a/audio b/audio @@ -0,0 +1,7 @@ +#!/bin/sh + +pulseaudio-control \ + --sink-blacklist "alsa_output.pci-0000_29_00.1.hdmi-stereo-extra5" \ + --sink-nickname "alsa_output.pci-0000_2b_00.4.analog-stereo:Repro" \ + --sink-nickname "alsa_output.usb-C-Media_Electronics_Inc___Trust_GXT_363_headset-00.analog-stereo:Headset" \ + --format="\${VOL_LEVEL}% \$SINK_NICKNAME" $@ diff --git a/bar-update b/bar-update @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "$1" > $TMSBAR_PIPE diff --git a/bmadd b/bmadd @@ -0,0 +1,21 @@ +#!/usr/bin/env zsh + +usage="usage: <name> <url> [<tags>]" + +if [[ ! -f ${BOOKMARK} ]]; then + echo "BOOKMARK env is not set or not a file" >&2 + exit 1 +fi + +function add() { + name=$1 + url=$2 + tags=$3 + if [[ -z "$name" || -z "$url" ]]; then + echo $usage >&2 + exit 1 + fi + printf "[%s](%s)%s\n" $name $url $tags >&1 >> $BOOKMARK +} + +add "$@" diff --git a/bmls b/bmls @@ -0,0 +1,35 @@ +#!/usr/bin/env zsh + +usage="usage: [<name> <url>]" + +if [[ ! -f ${BOOKMARK} ]]; then + echo "BOOKMARK env is not set or not a file" >&2 + exit 1 +fi + +function list() { + name=$1 + url=$2 + + # Format search string + if [[ -n $name ]]; then + search_string="\[.*${name}.*\]" + fi + if [[ -n $url ]]; then + search_string+="(.*${url}.*)" + fi + + if [[ -z $search_string ]]; then # List bookmarks + cat $BOOKMARK + exit 0 + else # Search bookmarks + g=$(grep -i "$search_string" < $BOOKMARK) + if [[ -z $g ]];then + echo "Not found" >&2 + exit 0 + fi + echo $g + fi +} + +list "$@" diff --git a/bmmenu b/bmmenu @@ -0,0 +1,45 @@ +#!/usr/bin/env zsh + +if [[ ! -f ${BOOKMARK} ]]; then + echo "BOOKMARK env is not set or not a file" >&2 + exit 1 +fi + +key="templates" +newwin="" +while test $# -gt 0; do + case "$1" in + --new-window) + newwin="--new-window" + shift + ;; + *) + break + ;; + esac +done + +[[ $DEBUG == 1 ]] && echo "newwin=$newwin" + +function open() { + bmark=$1 + [[ -n $bmark ]] || return + + title="$(awk 'BEGIN{FS=" "} {print $1}' <<< $bmark)" + url="$(awk 'BEGIN{FS=" "} {print $2}' <<< $bmark)" + tags="$(awk 'BEGIN{FS=" "} {print $3}' <<< $bmark)" + + if [[ "$tags" =~ "$key" ]]; then + add=$(echo "" | dmenu -p $url) + $BROWSER $newwin $url$add + else + $BROWSER $newwin $url + fi +} + +sed -E 's/\[(.*)\]\((.*)\) ?(.*)?/\1 \2 \3/' $BOOKMARK | dmenu -i -p bmark: -l 8 | +while IFS= read -r line +do + [[ $DEBUG == 1 ]] && echo "line=$line" + open "$line" +done diff --git a/bmrm b/bmrm @@ -0,0 +1,54 @@ +#!/usr/bin/env zsh + +usage="usage: <name> <url>" + +if [[ ! -f ${BOOKMARK} ]]; then + echo "BOOKMARK env is not set or not a file" >&2 + exit 1 +fi + +function confirm() { + echo "$1" + vared -p "Remove (y/n): " -c ok + if [[ $ok == "y" ]]; then + sed -i "${2}d" $BOOKMARK + fi +} + +function remove() { + name="$1" + url="$2" + + # Format search string + [[ -n $name ]] || exit 1 + search_string="\[.*${name}.*\]" + if [[ -n $url ]]; then + search_string+="(.*${url}.*)" + fi + + # Search bookmarks + g=$(grep -in "$search_string" < $BOOKMARK) + if [[ -z $g ]];then + echo "Not found" >&2 + exit 0 + fi + count=$(wc -l <<< "$g") + + # Selection, Confirm and remove + if [[ $count -gt 1 ]]; then + echo $g + vared -p "Number: " -c num + + if [[ $num != <-> ]]; then + echo "Not a number" + exit 1 + fi + + confirm "$(sed -n "/^$num/p" <<< "$g")" "$num" + else + num=$(awk 'BEGIN{FS=":"}{print $1}' <<< "$g") + confirm "$g" "$num" + fi +} + +remove "$@" diff --git a/bspswallow b/bspswallow @@ -0,0 +1,53 @@ +#!/bin/sh + +# Get class of a wid +get_class() { + id=$1 + if [ -z "$id" ]; then + echo "" + else + xprop -id "$id" | sed -n '/WM_CLASS\|WM_COMMAND/s/.*"\(.*\)".*/\1/p' + fi +} + +swallow() { + addedtodesktop=$2 + lasttermdesktop=$(bspc query -D -n last) + + swallowerid=$1 + swallowingid=$(bspc query -N -n last) + + if [ "$addedtodesktop" = "$lasttermdesktop" ]; then + cat ~/.config/bspwm/noswallow ~/.config/bspwm/terminals | grep "^$(get_class "$swallowerid")$" && return + grep "^$(get_class "$swallowingid")$" ~/.config/bspwm/terminals || return + echo "$swallowerid $swallowingid" >> /tmp/swallowids + bspc node "$swallowingid" --flag hidden=on + fi +} + +spit() { + spitterid=$1 + spitterdesktop=$2 + grep "^$spitterid" /tmp/swallowids || return + spittingid=$(grep "^$spitterid" /tmp/swallowids | head -n1 | awk '{print $2}') + + bspc node "$spittingid" --flag hidden=off + + termdesktop=$(bspc query -D -n "$spittingid") + [ "$termdesktop" = "$spitterdesktop" ] || bspc node "$spittingid" -d "$spitterdesktop" + + bspc node "$spittingid" -f + sed -i "/^$spitterid/d" /tmp/swallowids +} + +bspc subscribe node_add node_remove | while read -r event +do + case $(echo "$event" | awk '{ print $1 }') in + node_add) + swallow $(echo "$event" | awk '{print $5 " " $3}') + ;; + node_remove) + spit $(echo "$event" | awk '{print $4 " " $3}') + ;; + esac +done diff --git a/chcol b/chcol @@ -0,0 +1,30 @@ +#!/bin/zsh + +theme="$1" +themes=(light dark) +curr_theme=${THEME:-$CONFIG/theme} + +if [[ -z "$theme" ]]; then + echo "USAGE: chcol <light|dark>" + exit 1 +fi + +if [[ ! -v SCHEME ]]; then + echo "SCHEME env is not set" >&2 + exit 1 +fi + +if (($themes[(Ie)$theme])); then + echo "$theme" > $curr_theme + # Terminal + sed "s/^colors:.*/colors: *${SCHEME}_$theme/" -i $CONFIG/alacritty/alacritty.yml + # nevim + for sname in $(nvr --serverlist); do nvr --servername "$sname" -cc "set background=${theme}" --nostart -s &; done + # Neomutt from $theme file (needs restart) + exit 0 +else + echo "'$theme' is not a valid theme" + echo "USAGE: chcol <light|dark>" + exit 1 +fi + diff --git a/dmenu_pass b/dmenu_pass @@ -0,0 +1,17 @@ +#!/bin/lua + +-- get pswd from dmenu +local f = io.popen('1pass | dmenu ' .. table.concat(arg, ' ')) +local name = f:read('*l') + +if name then + local f2 = io.popen(string.format('1pass -p "%s" username', name)) + local username = f2:read('*l'); + if username then + os.execute(string.format('1pass "%s"', name)) + os.execute(string.format('notify-send "%s" "%s"', name, username)) + end +else + return +end + diff --git a/dmenu_path b/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu_run b/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dpgwdev b/dpgwdev @@ -0,0 +1,38 @@ +#!/bin/lua + +local ui = 'pub get && pub run build_runner serve web:8081 | track_run dart ui' +local ui_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local dw = 'pub get && pub run build_runner serve web:8082 | track_run dart dw' +local dw_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local digi = 'pub get && pub run build_runner serve web:8083 | track_run dart digi' +local digi_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local dpgw = 'mvn compile && mvn exec:java -Dexec.mainClass=org.medoro.dpgw.DPGWMain' +local dg = '~/dev/medoro/dpgw' + +os.execute(string.format('tmux new -s medoro -c %s -n %s -d', dg .. '/dpgw', 'dpgw')) +os.execute(string.format('tmux send-keys -t %s "%s" enter', 'dpgw', dpgw)) + +for _, a in ipairs(arg) do + if a == 'ui' then + print 'ui+sass' + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'ui', dg .. '/ui')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'ui', ui)) + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'ui_sass', dg .. '/ui')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'ui_sass', ui_sass)) + os.execute(string.format('tmux join-pane -t %s -s %s -t %s', 'medoro', 'ui_sass', 'ui')) + elseif a == 'dw' then + print 'dw+sass' + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'dw', dg .. '/dw')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'dw', dw)) + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'dw_sass', dg .. '/dw')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'dw_sass', dw_sass)) + os.execute(string.format('tmux join-pane -t %s -s %s -t %s', 'medoro', 'dw_sass', 'dw')) + elseif a == 'digi' then + print 'digi+sass' + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'digi', dg .. '/digi')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'digi', digi)) + os.execute(string.format('tmux neww -t %s: -n %s -c %s -d', 'medoro', 'digi_sass', dg .. '/digi')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'digi_sass', digi_sass)) + os.execute(string.format('tmux join-pane -t %s -s %s -t %s', 'medoro', 'digi_sass', 'digi')) + end +end diff --git a/dpgwr b/dpgwr @@ -0,0 +1,37 @@ +#!/bin/lua + +local ui = 'pub get && pub run build_runner serve web:8081 | track_run dart ui' +local ui_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local dw = 'pub get && pub run build_runner serve web:8082 | track_run dart dw' +local dw_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local digi = 'pub get && pub run build_runner serve web:8083 | track_run dart digi' +local digi_sass = 'sass --no-source-map --style compressed --load-path web/mdc --watch .' +local dpgw = 'mvn compile && mvn exec:java -Dexec.mainClass=org.medoro.dpgw.DPGWMain' + +for _, a in ipairs(arg) do + if a == 'ui' then + print 'ui+sass' + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:ui.0')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:ui.0', ui)) + -- sass + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:ui.1')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:ui.1', ui_sass)) + elseif a == 'dw' then + print 'dw+sass' + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:dw.0')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:dw.0', dw)) + -- sass + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:dw.1')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:dw.1', dw_sass)) + elseif a == 'digi' then + print 'digi+sass' + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:digi.0')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:digi.0', digi)) + -- sass + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:digi.1')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:digi.1', digi_sass)) + elseif a == 'dpgw' then + os.execute(string.format('tmux send-keys -t %s "^c"', 'medoro:dpgw')) + os.execute(string.format('tmux send-keys -t %s "%s" enter', 'medoro:dpgw', dpgw)) + end +end diff --git a/dunst-reload b/dunst-reload @@ -0,0 +1,4 @@ +#!/bin/env zsh + +killall dunst +notify-send foo diff --git a/external_rules b/external_rules @@ -0,0 +1,12 @@ +#!/bin/lua + +local wid = arg[1] +local class = arg[2] +local instance = arg[3] +local consequences = arg[4] + +local cmd = io.popen(string.format('xprop -id %s %s', wid, 'WM_WINDOW_ROLE')) +if cmd:read('l'):find('pop%-up') then + print('state=floating') +end +cmd:close() diff --git a/killclass.sh b/killclass.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +if [ -z $1 ]; then + echo "Usage: $0 <name of window>" + exit 1 +fi + +winids=$(xdotool search --classname ${1}) +for winid in $winids; do + echo "Killing $winid" + kill -9 $(xdotool getwindowpid $winid) +done diff --git a/mail_check.lua b/mail_check.lua @@ -0,0 +1,43 @@ +#!/bin/lua + +local mbsync = require 'tms.mbsync' +local acc = mbsync.accounts() + +local function notify(title, body) + local h = io.popen(string.format( + 'dunstify --action=\'open,Open\' --appname=\'neomutt\' \'%s\' \'%s\' &', + title, body)) + for a in h:lines() do + if a == 'open' then + os.execute('alacritty -e neomutt &') + end + end +end + +local function on_change() os.execute('bar-update mail') end + +local function check(dirs) + local cmd = string.format('inotifywait -m -r %s %s -q', + table.concat({'-e create', '-e move', '-e delete'}, ' '), + table.concat(dirs, ' ')) + local handler = io.popen(cmd) + for output in handler:lines() do + local _, _, _ = output:match('(.*) (.*) (.*)') + for _, _ in pairs(acc) do + -- if path:find(string.format("INBOX/new", a)) and action == "MOVED_TO" then + -- print('moved to now') + -- notify(string.format("MAIL:%s", a), "subject on the way") + -- end + + print('change') + on_change() + end + + end +end + +local dirs = {} +for _, a in pairs(acc) do + table.insert(dirs, string.format('%s/.mail/%s/INBOX', os.getenv('HOME'), a)) +end +check(dirs) diff --git a/mail_count.lua b/mail_count.lua @@ -0,0 +1,23 @@ +#!/bin/lua + +local mbsync = require('tms.mbsync') +local acc = mbsync.accounts() + +local function count(a) + local h = io.popen(string.format('fd . %s/.mail/%s/INBOX/new | wc -l', os.getenv('HOME'), a)) + return tonumber(h:read()) +end + +local format = {} +for _, a in pairs(acc) do + local c = count(a) + if c > 0 then + table.insert(format, string.format('%s:%s', a, c)) + end +end + +if #format == 0 then + print('') +else + print(table.concat(format, ' ')) +end diff --git a/mdev b/mdev @@ -0,0 +1,308 @@ +#!/bin/lua + +----------------------------- +------ CONFIGURATION ------- +----------------------------- +local session = 'medoro' -- name for tmux session +local workdir = '~/dev/medoro/dpgw' -- your root dir for projects + +local cmd = { + ui = 'pub get && pub run build_runner serve --delete-conflicting-outputs web:8081 >&1 > >(trun dart ui)', + ui_sass = 'sass --no-source-map --watch .', + dw = 'pub get && pub run build_runner serve --delete-conflicting-outputs web:8082 >&1 > >(trun dart dw)', + dw_sass = 'sass --no-source-map --watch .', + digi = 'pub get && pub run build_runner serve --delete-conflicting-outputs web:8083 >&1 > >(trun dart digi)', + digi_sass = 'sass --no-source-map --watch .', + dpgw = 'mvn compile && mvn exec:java -Dexec.mainClass=org.medoro.dpgw.DPGWMain', +} +------ APP DEFINITION ------- +-- cwd: working directory +-- cmd: command to start/restart +local dpgw = {key = 'dpgw', cwd = workdir .. '/dpgw', cmd = cmd.dpgw} +local ui = {key = 'ui', cwd = workdir .. '/ui', cmd = cmd.ui} +local uis = {key = 'uis', cwd = workdir .. '/ui', cmd = cmd.ui_sass} +local dw = {key = 'dw', cwd = workdir .. '/dw', cmd = cmd.dw} +local dws = {key = 'dws', cwd = workdir .. '/dw', cmd = cmd.dw_sass} +local digi = {key = 'digi', cwd = workdir .. '/digi', cmd = cmd.digi} +local digis = {key = 'digis', cwd = workdir .. '/digi', cmd = cmd.digi_sass} +-- COUPLES +-- apps: list of app to make action on +local stack = {key = 'stack', apps = {dpgw, {ui, uis}, {dw, dws}}} +local dwsass = {key = 'dwsass', apps = {{dw, dws}}} +local uisass = {key = 'uisass', apps = {{ui, uis}}} +local digisass = {key = 'digisass', apps = {{digi, digis}}} +------------------------------ + +------- ENABLED APPS ------- +local enabled_apps = {ui, uis, dw, dws, dpgw, digi, digis, dwsass, uisass, digisass, stack} +----------------------------- + +------- USAGE ------- +local usage = [[ +usage: mdev <action> [<app>] + +action + s <app> -- [s]tart | Start app in tmux session + r <app> -- [r]estart | Cancel command and rerun app cmd + t [<app>] -- [t]erm | Stop app or whole session if app not provided + o [<app>] -- [o]pen | Open tmux, if <app> is provided open app pane + (Only first app is taken) + j <app>... -- [j]oin | Join multiple app to single tmux window + +start + app separated by space will start each one in new tmux window + > "app1 app2 app3" + + to start multple apps in one tmux window, connect them with '+' + > "app1+app2 app3" +]] +----------------------------- + +-- provide at least one argument or catch help for usage +if #arg == 0 or arg[1] == '-h' or arg[1] == '--help' then + io.write(usage, '\n') + os.exit(0) +end + +local actions = {start = 's', restart = 'r', stop = 't', open = 'o', join = 'j'} -- command mapping +local action_set = {['s'] = true, ['r'] = true, ['t'] = true, ['o'] = true, ['j'] = true} +local action = string.sub(table.remove(arg, 1), 1, 1); -- get first char of command +-- check for valid command +if not action_set[action] then + io.write(usage, '\n') + os.exit(1) +end + +-- make action +if #arg == 0 and action == actions.stop then + local _, _, code = os.execute(string.format('tmux kill-session -t %s', session)) + if code < 1 then + io.write('Stopped session', '\n') + end + os.exit(0) +elseif #arg == 0 then + os.exit(0) +end + +----------------------------- +------ PROCESS UTILS ------- +----------------------------- +local function started() + local _, _, code = os.execute(string.format('tmux has -t %s 2>> /dev/null', session)) + return code < 1 +end +local function run(cmd, ...) return os.execute(string.format(cmd, ...)) end + +-------------------------- +------ TMUX UTILS ------- +-------------------------- +local function tlocation(pane) + local list = io.popen(string.format( + 'tmux lsp -s -t %s -F "#{pane_title} #{window_index} #{pane_index}" 2>> /dev/null', + session)) + local location + for line in list:lines() do + local p, wi, pi = string.match(line, '^(%a+) (%d+) (%d+)$') + if p == pane then + location = string.format('%s:%s.%s', session, wi, pi) + end + end + return location +end +local function tattach(pane) + local location = tlocation(pane) + if location then + run('tmux a -t %s', location) + end +end +local function tkillp(pane) + local location = tlocation(pane) + if location then + run('tmux killp -t %s', location) + end +end +local function tnew(key, cwd) + run('tmux neww -c %s -t %s -n %s', cwd, session, key) + run('tmux selectp -t %s:%s.0 -T %s', session, key, key) +end +local function tsend(pane, cmds) + local location = tlocation(pane) + if location then + if type(cmds) == 'table' then + for _, c in ipairs(cmds) do + run('tmux send -t %s "%s" enter', location, c) + end + else + run('tmux send -t %s "%s" enter', location, cmds) + end + return true + end +end +local function tjoin(target, source) + local spane = tlocation(source) + local tpane = tlocation(target) + run('tmux joinp -s %s -t %s', spane, tpane) +end + +------------------ +------ APP ------- +------------------ +local App = {} + +------ METATABLE ------- +function App:new(app) + self.__index = self + setmetatable(app, self) + app.run = {} + return app +end +------------------------ + +function App:start() + tnew(self.key, self.cwd) + tsend(self.key, self.cmd) + io.write(string.format('Started %s', self.key), '\n') +end + +function App:restart() + tsend(self.key, {'^c', self.cmd}) + io.write(string.format('Restarted %s', self.key), '\n') +end + +function App:stop() + tkillp(self.key, self.key) + io.write(string.format('Stopped %s', self.key), '\n') + -- TODO: check if window is not empty and close it.. maybe +end + +function App:open() + local location = tlocation(self.key) + if location then + tattach(self.key) + end +end + +--- if pane exists +function App:started() + return tlocation(self.key) + -- TODO: maybe check if command is running, is that possible? +end + +------ CREATE APPS ------- +local apps = {} +for _, app in ipairs(enabled_apps) do + apps[app.key] = App:new(app) +end +-------------------------- + +------ SESSION STARTED CHECK ------- +local fwin = 'initialize' -- name of first pane created alongside session +-- on START action - if session is not started, create new +if action == actions.start and not started() then + local ok = run('tmux new -s %s -n %s -d', session, fwin) + if not ok then + io.stderr.write('Could not run session.') + os.exit(1) + end + io.write(string.format('Started session "%s"', session), '\n') +end +-- on NON-START action - if session is not started, exit +if action ~= actions.start and not started() then + io.stderr:write('Session not running! Start session first.\n') + os.exit(1) +end +------------------------------------ + +------ PARSE ARGS ------- +local curr_apps = {} +for _, a in ipairs(arg) do + local key_sequence = {} + for k, _ in string.gmatch(a, '(%a+)%+?') do + local app = apps[k] + if app then + -- COUPLES CHECK + -- expand couples + if app.apps then + for _, couple_apps in ipairs(app.apps) do + table.insert(curr_apps, couple_apps) + end + else + table.insert(key_sequence, app) + end + else + io.stderr:write('App with key "', k, '" not exists, ignoring.', '\n') + end + end + table.insert(curr_apps, key_sequence) +end + +------ MAIN LOGIC ------- +for _, capp in ipairs(curr_apps) do + local slaves = {} -- apps to join + if not capp.key then + for _, ca in ipairs(capp) do + table.insert(slaves, ca) + end + else + table.insert(slaves, capp) + end + + for _, app in ipairs(slaves) do + if action == actions.start then + if app:started() then + io.stderr:write(string.format('App \'%s\' is already running.\n', app.key)) + else + app:start() + end + + elseif action == actions.restart then + if not app:started() then + io.stderr:write(string.format('App \'%s\' not running.\n', app.key)) + else + app:restart() + end + + elseif action == actions.stop then + if not app:started() then + io.stderr:write(string.format('App \'%s\' not running.\n', app.key)) + else + app:stop() + end + + elseif action == actions.open then + if not app:started() then + io.stderr:write(string.format('App \'%s\' not running.\n', app.key)) + else + app:open() + end + end + end + + -- join slaves to master + if action == actions.start or action == actions.join then + local master = table.remove(slaves, 1) + for _, app in ipairs(slaves) do + tjoin(master.key, app.key) + end + end +end +------------------------ + +---------------------- +------ CLEANUP ------- +---------------------- +local anything_running = false +for _, app in pairs(apps) do + if app:started() then + anything_running = true + break + end +end +if anything_running then + -- kill initial pane when tmux is created + run('tmux killw -t %s:%s 2>> /dev/null', session, fwin) +else + -- kill whole session if nothing is running + run('tmux kill-session -t %s 2>> /dev/null', session) + io.write(string.format('killing session "%s"', session), '\n') +end diff --git a/newmails.sh b/newmails.sh @@ -0,0 +1,18 @@ +#!/bin/sh +accounts="nemi medoro pruty" + +newmails() { + acc="$(echo $account)" + new=$(find "$HOME/.mail/$acc/INBOX/new/" -type f 2> /dev/null) + newcount=$(echo "$new" | sed '/^\s*$/d' | wc -l) +} + +out='' +for account in $accounts; do + newmails + if [ $(echo $newcount) -gt 0 ]; then + out="$out $account:$newcount" + fi +done + +echo $out diff --git a/newrss b/newrss @@ -0,0 +1,55 @@ +#!/bin/lua + +local filename = os.getenv("XDG_CACHE_HOME") .. "/newsboat" + +local action = arg[1] + +-- cachefile +local cache_file = io.open(filename) +if cache_file == nil then + os.execute("touch " .. filename) + cache_file = io.open(filename) +end +local cache = cache_file:read("*a") +local unread_cache, diff_cache = cache:match("(%d+) (%d+)") -- last unread +unread_cache = tonumber(unread_cache) or 0 +diff_cache = tonumber(diff_cache) or 0 +cache_file:close() + +-- get actual unread (no fetching) +local function get_unread() + local handle = io.popen("newsboat -x print-unread") + local unread_raw = handle:read() or unread_cache + handle:close() + return tonumber(string.match(unread_raw, "(%d+)")) -- actual unread +end + +-- print data out +local function fmt(format) + if format == "polybar" then + io.write(string.format("RSS %d-%d", unread_cache, diff_cache, '\n')) + else + io.write(unread_cache, " ", diff_cache, "\n") + end +end + +-- update cache file with actual unread (not fetching) +local function update_cache() + local unread = get_unread() + local diff = unread - unread_cache + if diff < 1 then + diff = 0 + end + io.open(filename, "w+"):write(unread, " ", diff):close() +end + +-- handle +if action == "reload" then + os.execute("newsboat -x reload") + update_cache() +elseif action == "update" then + update_cache() + fmt(arg[2]) +else + fmt(action) +end diff --git a/noswallow_dmenu b/noswallow_dmenu @@ -0,0 +1,17 @@ +#!/bin/sh +# Allows for dmenu_run to be used without swallowing the terminal + +killall bspswallow +dmenu_run "$@" + +# Hang until dmenu is closed +while pidof dmenu; do continue +done + +#If you frequently use dmenu_run without spawing a window commentout this while loop and uncomment the commented out part below +# bspc subscribe node_add | while read -r event; do +# bspswallow & exit +# done + +sleep 2 +bspswallow & exit diff --git a/noswallow_open b/noswallow_open @@ -0,0 +1,7 @@ +#!/bin/sh +# Prefix to a command to make it so that any windows spawned from it don't swallow the terminal +# for launching things from sxhd etc +killall bspswallow +eval $@ >/dev/null +sleep 2 +bspswallow & diff --git a/openhtml b/openhtml @@ -0,0 +1,8 @@ +#!/bin/sh + +tmpfile=$(mktemp --suffix=.html) +cat $1 > $tmpfile +$BROWSER --new-window $tmpfile >> /dev/null +sleep 1 +rm $tmpfile + diff --git a/pass-section b/pass-section @@ -0,0 +1,48 @@ +#!/bin/env bash + +shopt -s nullglob globstar + +clip=0 +while test $# -gt 0; do + case "$1" in + --clip) + clip=1 + shift + ;; + -c) + clip=1 + shift + ;; + *) + break + ;; + esac +done + + +name="$1" +if [[ ! -n $name ]]; then + echo "Usage: pass-section <pass-name> <pass-section>" + exit 1 +fi +part="$2" +if [[ ! -n $part ]]; then + echo "Usage: pass-section <pass-name> <pass-section>" + exit 1 +fi +data=`pass "$name"` + +section="$(awk -F: '/'"${part}"'/{gsub(/ /,"");print $2}' <<< "$data")" +if [[ ! -n $section ]]; then + echo "Section not exist" + exit 1 +fi + + +if [[ $clip -eq 0 ]]; then + echo $section +else + echo "Section copied to clipboard." + xclip -sel clip <<< $section +fi + diff --git a/passmenu b/passmenu @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +shopt -s nullglob globstar + +typeit=0 +otp=0 +while test $# -gt 0; do + case "$1" in + --type) + typeit=1 + shift + ;; + --otp) + otp=1 + shift + ;; + *) + break + ;; + esac +done + +prefix=${PASSWORD_STORE_DIR-~/.password-store} +password_files=( "$prefix"/**/*.gpg ) +password_files=( "${password_files[@]#"$prefix"/}" ) +password_files=( "${password_files[@]%.gpg}" ) + +password=$(printf '%s\n' "${password_files[@]}" | dmenu "$@") + +[[ -n $password ]] || exit + +login=$(pass "$password" | awk -F: '/^login/{print $2}' | tr -d ' ') +if [[ -n $login ]]; then + if [[ $otp -eq 0 ]]; then + notify-send "$password" "$login" + else + notify-send "$password - OTP copied" + fi +fi + +if [[ $typeit -eq 0 ]]; then + if [[ $otp -eq 0 ]]; then + pass show -c "$password" 2>/dev/null + else + pass otp -c "$password" 2>/dev/null + fi +else + if [[ $otp -eq 0 ]]; then + pass show "$password" | { IFS= read -r pass; printf %s "$pass"; } | xdotool type --clearmodifiers --file - + else + pass otp "$password" | { IFS= read -r pass; printf %s "$pass"; } | xdotool type --clearmodifiers --file - + fi +fi + diff --git a/passmenu_section b/passmenu_section @@ -0,0 +1,19 @@ +#!/bin/env bash + +shopt -s nullglob globstar + +prefix=${PASSWORD_STORE_DIR-~/.password-store} +password_files=( "$prefix"/**/*.gpg ) +password_files=( "${password_files[@]#"$prefix"/}" ) +password_files=( "${password_files[@]%.gpg}" ) + +password=$(printf '%s\n' "${password_files[@]}" | dmenu "$@") + +[[ -n $password ]] || exit + +data=`pass "$password"` +part="$((awk -F: '{print $1}' <<< "$data") | sed '1d' | dmenu)" + +[[ -n $part ]] || exit + +awk -F: '/'"${part}"'/{gsub(/ /,"");print $2}' <<< "$data" | xclip -sel clip && notify-send "$password" "$part" diff --git a/playerctl-custom.sh b/playerctl-custom.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +a="`dbus-send --reply-timeout=1000 --print-reply --dest="org.mpris.MediaPlayer2.spotifyd" /org/mpris/MediaPlayer2 "org.freedesktop.DBus.Properties.Get" string:"org.mpris.MediaPlayer2.Player" string:"PlaybackStatus"`" +status="`echo $a | sed -n 's/.* string \"\(.*\)\"/\1/p'`" +echo $status diff --git a/pulseaudio-control b/pulseaudio-control @@ -0,0 +1,518 @@ +#!/usr/bin/env bash + +################################################################## +# Polybar Pulseaudio Control # +# https://github.com/marioortizmanero/polybar-pulseaudio-control # +################################################################## + +# Defaults for configurable values, expected to be set by command-line arguments +AUTOSYNC="no" +COLOR_MUTED="%{F#6b6b6b}" +ICON_MUTED= +ICON_SINK= +NOTIFICATIONS="no" +OSD="no" +SINK_NICKNAMES_PROP= +VOLUME_STEP=${INC:-2} +VOLUME_MAX=130 +# shellcheck disable=SC2016 +FORMAT='$VOL_ICON ${VOL_LEVEL}% $ICON_SINK $SINK_NICKNAME' +declare -A SINK_NICKNAMES +declare -a ICONS_VOLUME +declare -a SINK_BLACKLIST + +# Environment & global constants for the script +export LANG=en_US # Some calls depend on English outputs of pactl +END_COLOR="%{F-}" # For Polybar colors + + +# Saves the currently default sink into a variable named `curSink`. It will +# return an error code when pulseaudio isn't running. +function getCurSink() { + if ! pactl info &>/dev/null; then return 1; fi + local curSinkName + + curSinkName=$(pactl info | awk '/Default Sink: / {print $3}') + curSink=$(pactl list sinks | grep -B 4 -E "Name: $curSinkName\$" | sed -nE 's/^Sink #([0-9]+)$/\1/p') +} + + +# Saves the sink passed by parameter's volume into a variable named `VOL_LEVEL`. +function getCurVol() { + VOL_LEVEL=$(pactl list sinks | grep -A 15 -E "^Sink #$1\$" | grep 'Volume:' | grep -E -v 'Base Volume:' | awk -F : '{print $3; exit}' | grep -o -P '.{0,3}%' | sed 's/.$//' | tr -d ' ') +} + + +# Saves the name of the sink passed by parameter into a variable named +# `sinkName`. +function getSinkName() { + sinkName=$(pactl list sinks short | awk -v sink="$1" '{ if ($1 == sink) {print $2} }') + portName=$(pactl list sinks | grep -e 'Sink #' -e 'Active Port: ' | sed -n "/^Sink #$1\$/,+1p" | awk '/Active Port: / {print $3}') +} + + +# Saves the name to be displayed for the sink passed by parameter into a +# variable called `SINK_NICKNAME`. +# If a mapping for the sink name exists, that is used. Otherwise, the string +# "Sink #<index>" is used. +function getNickname() { + getSinkName "$1" + unset SINK_NICKNAME + + if [ -n "$sinkName" ] && [ -n "$portName" ] && [ -n "${SINK_NICKNAMES[$sinkName/$portName]}" ]; then + SINK_NICKNAME="${SINK_NICKNAMES[$sinkName/$portName]}" + elif [ -n "$sinkName" ] && [ -n "${SINK_NICKNAMES[$sinkName]}" ]; then + SINK_NICKNAME="${SINK_NICKNAMES[$sinkName]}" + elif [ -n "$sinkName" ] && [ -n "$SINK_NICKNAMES_PROP" ]; then + getNicknameFromProp "$SINK_NICKNAMES_PROP" "$sinkName" + # Cache that result for next time + SINK_NICKNAMES["$sinkName"]="$SINK_NICKNAME" + fi + + if [ -z "$SINK_NICKNAME" ]; then + SINK_NICKNAME="Sink #$1" + fi +} + +# Gets sink nickname based on a given property. +function getNicknameFromProp() { + local nickname_prop="$1" + local for_name="$2" + + SINK_NICKNAME= + while read -r property value; do + case "$property" in + Name:) + sink_name="$value" + unset sink_desc + ;; + "$nickname_prop") + if [ "$sink_name" != "$for_name" ]; then + continue + fi + SINK_NICKNAME="${value:3:-1}" + break + ;; + esac + done < <(pactl list sinks) +} + +# Saves the status of the sink passed by parameter into a variable named +# `isMuted`. +function getIsMuted() { + isMuted=$(pactl list sinks | grep -E "^Sink #$1\$" -A 15 | awk '/Mute: / {print $2}') +} + + +# Saves all the sink inputs of the sink passed by parameter into a string +# named `sinkInputs`. +function getSinkInputs() { + sinkInputs=$(pactl list sink-inputs | grep -B 4 "Sink: $1" | sed -nE 's/^Sink Input #([0-9]+)$/\1/p') +} + + +function volUp() { + # Obtaining the current volume from pulseaudio into $VOL_LEVEL. + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + getCurVol "$curSink" + local maxLimit=$((VOLUME_MAX - VOLUME_STEP)) + + # Checking the volume upper bounds so that if VOLUME_MAX was 100% and the + # increase percentage was 3%, a 99% volume would top at 100% instead + # of 102%. If the volume is above the maximum limit, nothing is done. + if [ "$VOL_LEVEL" -le "$VOLUME_MAX" ] && [ "$VOL_LEVEL" -ge "$maxLimit" ]; then + pactl set-sink-volume "$curSink" "$VOLUME_MAX%" + elif [ "$VOL_LEVEL" -lt "$maxLimit" ]; then + pactl set-sink-volume "$curSink" "+$VOLUME_STEP%" + fi + + if [ $OSD = "yes" ]; then showOSD "$curSink"; fi + if [ $AUTOSYNC = "yes" ]; then volSync; fi +} + + +function volDown() { + # Pactl already handles the volume lower bounds so that negative values + # are ignored. + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + pactl set-sink-volume "$curSink" "-$VOLUME_STEP%" + + if [ $OSD = "yes" ]; then showOSD "$curSink"; fi + if [ $AUTOSYNC = "yes" ]; then volSync; fi +} + + +function volSync() { + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + getSinkInputs "$curSink" + getCurVol "$curSink" + + # Every output found in the active sink has their volume set to the + # current one. This will only be called if $AUTOSYNC is `yes`. + for each in $sinkInputs; do + pactl set-sink-input-volume "$each" "$VOL_LEVEL%" + done +} + + +function volMute() { + # Switch to mute/unmute the volume with pactl. + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + if [ "$1" = "toggle" ]; then + getIsMuted "$curSink" + if [ "$isMuted" = "yes" ]; then + pactl set-sink-mute "$curSink" "no" + else + pactl set-sink-mute "$curSink" "yes" + fi + elif [ "$1" = "mute" ]; then + pactl set-sink-mute "$curSink" "yes" + elif [ "$1" = "unmute" ]; then + pactl set-sink-mute "$curSink" "no" + fi + + if [ $OSD = "yes" ]; then showOSD "$curSink"; fi +} + + +function nextSink() { + # The final sinks list, removing the blacklisted ones from the list of + # currently available sinks. + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + + # Obtaining a tuple of sink indexes after removing the blacklisted devices + # with their name. + sinks=() + local i=0 + while read -r line; do + index=$(echo "$line" | cut -f1) + name=$(echo "$line" | cut -f2) + + # If it's in the blacklist, continue the main loop. Otherwise, add + # it to the list. + for sink in "${SINK_BLACKLIST[@]}"; do + if [ "$sink" = "$name" ]; then + continue 2 + fi + done + + sinks[$i]="$index" + i=$((i + 1)) + done < <(pactl list short sinks | sort -n) + + # If the resulting list is empty, nothing is done + if [ ${#sinks[@]} -eq 0 ]; then return; fi + + # If the current sink is greater or equal than last one, pick the first + # sink in the list. Otherwise just pick the next sink avaliable. + local newSink + if [ "$curSink" -ge "${sinks[-1]}" ]; then + newSink=${sinks[0]} + else + for sink in "${sinks[@]}"; do + if [ "$curSink" -lt "$sink" ]; then + newSink=$sink + break + fi + done + fi + + # The new sink is set + pactl set-default-sink "$newSink" + + # Move all audio threads to new sink + local inputs + inputs="$(pactl list short sink-inputs | cut -f 1)" + for i in $inputs; do + pactl move-sink-input "$i" "$newSink" + done + + if [ $NOTIFICATIONS = "yes" ]; then + getNickname "$newSink" + + if command -v dunstify &>/dev/null; then + notify="dunstify --replace 201839192" + else + notify="notify-send" + fi + $notify "PulseAudio" "Changed output to $SINK_NICKNAME" --icon=audio-headphones-symbolic & + fi +} + + +# This function assumes that PulseAudio is already running. It only supports +# KDE OSDs for now. It will show a system message with the status of the +# sink passed by parameter, or the currently active one by default. +function showOSD() { + if [ -z "$1" ]; then + curSink="$1" + else + getCurSink + fi + getCurVol "$curSink" + getIsMuted "$curSink" + qdbus org.kde.kded /modules/kosd showVolume "$VOL_LEVEL" "$isMuted" +} + + +function listen() { + local firstRun=0 + + # Listen for changes and immediately create new output for the bar. + # This is faster than having the script on an interval. + pactl subscribe 2>/dev/null | { + while true; do + { + # If this is the first time just continue and print the current + # state. Otherwise wait for events. This is to prevent the + # module being empty until an event occurs. + if [ $firstRun -eq 0 ]; then + firstRun=1 + else + read -r event || break + # Avoid double events + if ! echo "$event" | grep -e "on card" -e "on sink" -e "on server"; then + continue + fi + fi + } &>/dev/null + output + done + } +} + + +function output() { + if ! getCurSink; then + echo "PulseAudio not running" + return 1 + fi + getCurVol "$curSink" + getIsMuted "$curSink" + + # Fixed volume icons over max volume + local iconsLen=${#ICONS_VOLUME[@]} + if [ "$iconsLen" -ne 0 ]; then + local volSplit=$((VOLUME_MAX / iconsLen)) + for i in $(seq 1 "$iconsLen"); do + if [ $((i * volSplit)) -ge "$VOL_LEVEL" ]; then + VOL_ICON="${ICONS_VOLUME[$((i-1))]}" + break + fi + done + else + VOL_ICON="" + fi + + getNickname "$curSink" + + # Showing the formatted message + if [ "$isMuted" = "yes" ]; then + # shellcheck disable=SC2034 + VOL_ICON=$ICON_MUTED + echo "${COLOR_MUTED}$(eval echo "$FORMAT")${END_COLOR}" + else + eval echo "$FORMAT" + fi +} + + +function usage() { + echo "\ +Usage: $0 [OPTION...] ACTION + +Options: [defaults] + --autosync | --no-autosync + Whether to maintain same volume for all programs. + Default: $AUTOSYNC + --color-muted <rrggbb> + Color in which to format when muted. + Default: ${COLOR_MUTED:4:-1} + --notifications | --no-notifications + Whether to show notifications when changing sinks. + Default: $NOTIFICATIONS + --osd | --no-osd + Whether to display KDE's OSD message. + Default: $OSD + --icon-muted <icon> + Icon to use when muted. + Default: none + --icon-sink <icon> + Icon to use for sink. + Default: none + --format <string> + Use a format string to control the output. + Available variables: + * \$VOL_ICON + * \$VOL_LEVEL + * \$ICON_SINK + * \$SINK_NICKNAME + Default: $FORMAT + --icons-volume <icon>[,<icon>...] + Icons for volume, from lower to higher. + Default: none + --volume-max <int> + Maximum volume to which to allow increasing. + Default: $VOLUME_MAX + --volume-step <int> + Step size when inc/decrementing volume. + Default: $VOLUME_STEP + --sink-blacklist <name>[,<name>...] + Sinks to ignore when switching. + Default: none + --sink-nicknames-from <prop> + pactl property to use for sink names, unless overriden by + --sink-nickname. Its possible values are listed under the 'Properties' + key in the output of \`pactl list sinks\` + Default: none + --sink-nickname <name>:<nick> + Nickname to assign to given sink name, taking priority over + --sink-nicknames-from. May be given multiple times, and 'name' is + exactly as listed in the output of \`pactl list sinks short | cut -f2\`. + Note that you can also specify a port name for the sink with + \`<name>/<port>\`. + Default: none + +Actions: + help display this message and exit + output print the PulseAudio status once + listen listen for changes in PulseAudio to automatically update + this script's output + up, down increase or decrease the default sink's volume + mute, unmute mute or unmute the default sink's audio + togmute switch between muted and unmuted + next-sink switch to the next available sink + sync synchronize all the output streams volume to be the same as + the current sink's volume + +Author: + Mario Ortiz Manero +More info on GitHub: + https://github.com/marioortizmanero/polybar-pulseaudio-control" +} + +while [[ "$1" = --* ]]; do + unset arg + unset val + if [[ "$1" = *=* ]]; then + arg="${1//=*/}" + val="${1//*=/}" + shift + else + arg="$1" + # Support space-separated values, but also value-less flags + if [[ "$2" != --* ]]; then + val="$2" + shift + fi + shift + fi + + case "$arg" in + --autosync) + AUTOSYNC=yes + ;; + --no-autosync) + AUTOSYNC=no + ;; + --color-muted|--colour-muted) + COLOR_MUTED="%{F#$val}" + ;; + --notifications) + NOTIFICATIONS=yes + ;; + --no-notifications) + NOTIFICATIONS=no + ;; + --osd) + OSD=yes + ;; + --no-osd) + OSD=no + ;; + --icon-muted) + ICON_MUTED="$val" + ;; + --icon-sink) + # shellcheck disable=SC2034 + ICON_SINK="$val" + ;; + --icons-volume) + IFS=, read -r -a ICONS_VOLUME <<< "$val" + ;; + --volume-max) + VOLUME_MAX="$val" + ;; + --volume-step) + VOLUME_STEP="$val" + ;; + --sink-blacklist) + IFS=, read -r -a SINK_BLACKLIST <<< "$val" + ;; + --sink-nicknames-from) + SINK_NICKNAMES_PROP="$val" + ;; + --sink-nickname) + SINK_NICKNAMES["${val//:*/}"]="${val//*:}" + ;; + --format) + FORMAT="$val" + ;; + *) + echo "Unrecognised option: $arg" >&2 + exit 1 + ;; + esac +done + +case "$1" in + up) + volUp + ;; + down) + volDown + ;; + togmute) + volMute toggle + ;; + mute) + volMute mute + ;; + unmute) + volMute unmute + ;; + sync) + volSync + ;; + listen) + listen + ;; + next-sink) + nextSink + ;; + output) + output + ;; + help) + usage + ;; + *) + echo "Unrecognised action: $1" >&2 + exit 1 + ;; +esac diff --git a/scratchpad_hide.sh b/scratchpad_hide.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +if [ -z $1 ]; then + echo "Usage: $0 <name of scratchpad window to hide>" + exit 1 +fi + +pids=$(xdotool search --classname ${1}) +for pid in $pids; do + echo "Toggle $pid" + bspc node $pid --flag hidden=on +done diff --git a/scratchpad_show.sh b/scratchpad_show.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +if [ -z $1 ]; then + echo "Usage: $0 <name of scratchpad window to show>" + exit 1 +fi + +pids=$(xdotool search --classname ${1}) +for pid in $pids; do + echo "Toggle $pid" + bspc node $pid --flag hidden=off -d focused -f +done diff --git a/scratchpad_toggle.sh b/scratchpad_toggle.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash + +if [ -z $1 ]; then + echo "Usage: $0 <name of hidden scratchpad window>" + exit 1 +fi + +pids=$(xdotool search --classname ${1}) +for pid in $pids; do + echo "Toggle $pid" + bspc node $pid --flag hidden -d focused -f +done diff --git a/sfc b/sfc @@ -0,0 +1,7 @@ +#!/bin/sh + +\ + SFEED_PLUMBER=sfopen \ + SFEED_YANKER="xclip -selection clipboard -rmlastnl -i" \ + SFEED_URL_FILE=$CONFIG/sfeed/archive \ + sfeed_curses $CACHE/sfeed/feeds/* diff --git a/sfcut b/sfcut @@ -0,0 +1,20 @@ +#!/bin/sh + +# TODO(tms) 23.05.2021: DIRECTORY + +feed="$1" +newfeed="$feed.new" + +if test -z $feed; then + echo "feed must be set" >&2 + exit 1 +fi + +awk -F '\t' -v "old=$(($(date +'%s') - 604800))" 'int($1) > old' < $feed > $newfeed + +bakfeed="$2" +if test -n "$bakfeed"; then + mv $feed $bakfeed +fi + +mv $newfeed $feed diff --git a/sfd b/sfd @@ -0,0 +1,5 @@ +#!/bin/sh + +url=$(sfeed_plain "$HOME/.sfeed/feeds/"* | dmenu -l 35 -i | \ + sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p') +test -n "${url}" && $BROWSER "${url}" diff --git a/sfeedmr b/sfeedmr @@ -0,0 +1,4 @@ +#!/bin/sh + +feeds=$CACHE/sfeed/feeds/n_* +sfeed_plain $feeds | sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p' | SFEED_URL_FILE=$CONFIG/sfeed/archive sfeed_markread read diff --git a/sfopen b/sfopen @@ -0,0 +1,15 @@ +#!/bin/lua + +local url = arg[1] +function open(format) os.execute(string.format(format, url)) end + +if string.find(url, "youtube") then + open("setsid -f mpv %s") +elseif string.find(url, "reddit") then + url = string.gsub(url, "www.reddit.com", "libredd.it") + open(os.getenv("BROWSER") .. " --new-window %s") +elseif string.find(url, "https") then + open(os.getenv("BROWSER") .. " --new-window %s") +else + open("xdg-open %s") +end diff --git a/sshmenu b/sshmenu @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +ssh_config=${SSH_CONFIG_FILE:-$HOME/.ssh/config} + +ssh_hosts=( $(grep "^Host\s\+[^*]" $ssh_config | cut -d " " -f 2-) ) +ssh_host=$(printf '%s\n' "${ssh_hosts[@]}" | dmenu -i "$@") + +if [ -n "$ssh_host" ]; then + alacritty -e zsh -c "TERM=xterm-256color ssh $ssh_host" +fi diff --git a/twitch-notifier.go b/twitch-notifier.go @@ -0,0 +1,128 @@ +//usr/bin/go run $0 $@; exit $? +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +const clientid = "b2cnkgs6ricpfzuimk03xt1vlsia4g" + +var ( + streams = []string{ + "476845395", // "bashbunni", + // "447179030", // "rwxrob", + "474725923", // "togglebit", + "167160215", // "theprimeagen", + "443849438", // "ashkankiani", + "71942329", // "acegikmo", + "114257969", // "teej_dv", + "233334675", // "battlestategames", + "73570785", // "marlliciuss", + "39661750", // "tensterakdary", + "106013742", // "pestily", + "423054112", // "jakobychill", + "63880003", // "anton", + "81463115", // "kotton", + "150900538", // "pehapkari", + } + + dunstify = flag.Bool("dunstify", false, "Use dunstify output") + fresh = flag.Int("fresh", 0, "Get streams started before N minutes") +) + +type Stream struct { + Id json.Number `json:"_id"` + StreamType string `json:"stream_type"` + Viewers uint `json:"viewers"` + CreatedAt time.Time `json:"created_at"` + Channel Channel +} + +type Channel struct { + Status string + Name string + Game string +} + +func main() { + flag.Parse() + + req, err := http.NewRequest("GET", "https://api.twitch.tv/kraken/streams?channel="+strings.Join(streams, ","), nil) + if err != nil { + log.Fatal(err) + } + req.Header.Add("Accept", "application/vnd.twitchtv.v5+json") + req.Header.Add("Client-ID", clientid) + + rsp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + defer rsp.Body.Close() + + bodyraw, err := io.ReadAll(rsp.Body) + if err != nil { + log.Fatal(err) + } + + var object struct{ Streams []Stream } + err = json.Unmarshal(bodyraw, &object) + if err != nil { + log.Fatal(err) + } + + now := time.Now() + forbiden := make(map[json.Number]bool) + + if *fresh > 0 { + duration, _ := time.ParseDuration("-" + strconv.Itoa(*fresh) + "m") + target := now.Add(duration) + for _, stream := range object.Streams { + if stream.CreatedAt.Before(target) { + forbiden[stream.Id] = true + } + } + } + + if *dunstify { + var sb strings.Builder + for _, stream := range object.Streams { + if forbiden[stream.Id] { + continue + } + uptime := now.Sub(stream.CreatedAt) + sb.WriteString(fmt.Sprintf("• <b>%s</b> (%d) - %s\n", stream.Channel.Name, stream.Viewers, fmtDuration(uptime))) + sb.WriteString(fmt.Sprintf(" %s\n", stream.Channel.Status)) + sb.WriteString(fmt.Sprintf(" <i>%s</i>\n", stream.Channel.Game)) + } + fmt.Print(sb.String()) + } else { + for _, stream := range object.Streams { + if forbiden[stream.Id] { + continue + } + fmt.Printf("%s\n", stream.Channel.Name) + } + } +} + +func fmtDuration(d time.Duration) string { + d = d.Round(time.Minute) + h := d / time.Hour + d -= h * time.Hour + m := d / time.Minute + return fmt.Sprintf("%02d:%02d", h, m) +} + +func remove(s []Stream, i int) []Stream { + s[i] = s[len(s)-1] + return s[:len(s)-1] +}