commit 5c62dfc64f48426196f024a004cd566da310bdc2
Author: Tomas Nemec <nemi@skaut.cz>
Date: Fri, 6 Aug 2021 15:13:11 +0200
init
Diffstat:
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]
+}