├── assets ├── Blocks.png ├── Usage.png ├── Blocks │ ├── disk.png │ ├── i3ws.png │ ├── vol.png │ ├── clock.png │ ├── music.png │ └── i3window.png ├── logo_light_centered.png ├── examplebar.sh ├── logo.svg ├── Usage.svg ├── Blocks.svg └── Logo_light.svg ├── block ├── clock ├── i3window ├── cpu ├── music ├── disk ├── vol └── i3ws ├── LICENSE ├── install.sh ├── .gitignore ├── genbar └── README.md /assets/Blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks.png -------------------------------------------------------------------------------- /assets/Usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Usage.png -------------------------------------------------------------------------------- /assets/Blocks/disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/disk.png -------------------------------------------------------------------------------- /assets/Blocks/i3ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/i3ws.png -------------------------------------------------------------------------------- /assets/Blocks/vol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/vol.png -------------------------------------------------------------------------------- /assets/Blocks/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/clock.png -------------------------------------------------------------------------------- /assets/Blocks/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/music.png -------------------------------------------------------------------------------- /assets/Blocks/i3window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/Blocks/i3window.png -------------------------------------------------------------------------------- /assets/logo_light_centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Snowlabs/bartender/HEAD/assets/logo_light_centered.png -------------------------------------------------------------------------------- /block/clock: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | interval=5 4 | 5 | while getopts hn: arg; do 6 | case "$arg" in 7 | n) 8 | interval="$OPTARG" 9 | ;; 10 | 11 | h) 12 | echo "Usage: $0 [-n interval] [-h] [date_format]" 13 | exit 0 14 | ;; 15 | ?) 16 | echo "-h for help" 17 | exit 1 18 | ;; 19 | esac 20 | done 21 | 22 | shift $((OPTIND - 1)) 23 | 24 | while true; do 25 | date "$@" 26 | sleep 5 & 27 | wait $! 28 | done 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sheheryar Parvaz, Javier Pollak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | has() { 4 | command -v $1 2>&1 > /dev/null 5 | return $? 6 | } 7 | 8 | get_prompt() { 9 | read ans 10 | 11 | # return user input 12 | if [ -n "$ans" ]; then 13 | echo "$ans" 14 | 15 | # return default 16 | else 17 | echo "$1" 18 | fi | sed -e "s|~|$HOME|" 19 | } 20 | 21 | ### SETUP 22 | if ! has git; then 23 | echo "git is not installed! Aborting" 1>&2 24 | exit 1 25 | fi 26 | 27 | echo 'Where should bartender be installed?' 28 | echo -n '[~/.local/share/bartender]: ' 29 | while :; do 30 | dir="$(get_prompt "$HOME/.local/share/bartender")" 31 | 32 | if [ -e "$dir" ]; then 33 | echo "There is already a directory at $dir" 34 | echo "Please choose another directory" 35 | echo "" 36 | continue 37 | else 38 | break 39 | fi 40 | done 41 | echo "" 42 | 43 | echo 'Where should the genbar utility be linked to?' 44 | echo -n '[~/bin]: ' 45 | bin="$(get_prompt "$HOME/bin")" 46 | [ ! -d "$bin" ] && mkdir -p "$bin" 47 | echo "" 48 | 49 | ### INSTALLATION 50 | git clone 'https://github.com/Snowlabs/bartender.git' "$dir" 51 | ln -s "$dir/genbar" "$bin/genbar" 52 | -------------------------------------------------------------------------------- /block/i3window: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import i3ipc 4 | import re 5 | import subprocess 6 | 7 | 8 | def print_window_name(): 9 | r = '' 10 | 11 | root = subprocess.Popen(['xprop', '-root'], stdout=subprocess.PIPE) 12 | 13 | for l in root.stdout: 14 | m = re.search('^_NET_ACTIVE_WINDOW.* ([\w]+)$', l.decode('utf-8')) 15 | if m is not None: 16 | window = subprocess.Popen(['xprop', '-id', m.group(1), 'WM_NAME'], 17 | stdout=subprocess.PIPE) 18 | break 19 | 20 | if window is not None: 21 | for l in window.stdout: 22 | m = re.match('WM_NAME\(\w+\) = \"(?P.+)\"$', 23 | l.decode('utf-8')) 24 | if m is not None: 25 | r = m.group('name') 26 | 27 | if len(r) >= 90: 28 | r = r[:87] + '...' 29 | else: 30 | pass 31 | 32 | print(r, flush=True) 33 | 34 | 35 | def on_window_event(self, e): 36 | print_window_name() 37 | 38 | 39 | if __name__ == '__main__': 40 | 41 | # Connect to i3 42 | i3 = i3ipc.Connection() 43 | 44 | # Initial workspaces print 45 | print_window_name() 46 | 47 | # Subscribe to events and start main loop 48 | i3.on('window::', on_window_event) 49 | i3.main() 50 | -------------------------------------------------------------------------------- /block/cpu: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() { 4 | echo "Usage: $0 [-n SLEEP] [-f PRECISION] TYPE" 5 | echo "Block for printing CPU information." 6 | echo "" 7 | 8 | echo "Arguments" 9 | echo " -n" "SLEEP" "\tUpdate interval in seconds." 10 | echo " -f" "PRECISION" "\tFloating point precision, when used." 11 | echo "" 12 | 13 | echo "TYPE corresponds to the type of cpu info to print. One of:" 14 | echo "\t""temp (default)" 15 | } 16 | 17 | cpu_temp() { 18 | if ! command -v > /dev/null sensors; then 19 | echo "lm_sensors is not installed, aborting." 1>&2 20 | exit 1 21 | fi 22 | 23 | sensors | awk -v p="$precision" ' 24 | BEGIN { 25 | core_count = 0 26 | temp_sum = 0 27 | } 28 | 29 | $1 == "Core" { 30 | core_count += 1 31 | temp_sum += $3 32 | } 33 | 34 | END { 35 | out = temp_sum/core_count 36 | printf "%." p "f\n", out 37 | } 38 | ' 39 | } 40 | 41 | loop() { 42 | case "$type" in 43 | temp) cmd=cpu_temp ;; 44 | *) 45 | echo "Invalid type. Check -h for more info." 1>&2 46 | exit 1 47 | esac 48 | 49 | while :; do 50 | eval "$cmd" 51 | sleep $sleep 52 | wait $! 53 | done 54 | } 55 | 56 | sleep=10 57 | precision=2 58 | while getopts n:f:h opt; do 59 | case "$opt" in 60 | n) 61 | sleep="$OPTARG" 62 | ;; 63 | f) 64 | precision="$OPTARG" 65 | ;; 66 | h) 67 | usage 68 | exit 0 69 | ;; 70 | ?) 71 | usage 72 | exit 1 73 | ;; 74 | esac 75 | done 76 | 77 | shift $((OPTIND - 1)) 78 | type="$@" 79 | 80 | loop 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # End of https://www.gitignore.io/api/python 104 | -------------------------------------------------------------------------------- /block/music: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | usage() { 4 | echo "Usage: $0 [-f FORMAT_STRING [-s SPACER_STRING]] -p PREV_STRING -n NEXT_STRING -P PLAY_STRING -S PAUSE_STRING" 1>&2 5 | echo "Example: $0 -f 'prev:toggle:next:---:title'" 1>&2 6 | } 7 | 8 | backend_mpc() { 9 | print_info() { 10 | playing=$(mpc status | sed -n '2p' | awk "{ print(\$1); }") 11 | if [ "$playing" = "[paused]" ]; then playing=0 12 | else playing=1 13 | fi 14 | 15 | export IFS=":" 16 | for block in $format; do 17 | case $block in 18 | "---") 19 | echo -n $spacer ;; 20 | toggle) 21 | echo -n "%{A:mpc toggle:}" 22 | if [ $playing -eq 1 ]; then echo -n $pause_s; 23 | else echo -n $play_s 24 | fi 25 | echo -n "%{A}" 26 | ;; 27 | play) echo -n "%{A:mpc play:}$play_s%{A}" ;; 28 | pause) echo -n "%{A:mpc pause:}$pause_s%{A}" ;; 29 | next) echo -n "%{A:mpc next:}$next_s%{A}" ;; 30 | prev) echo -n "%{A:mpc next:}$prev_s%{A}" ;; 31 | title) echo -n $(mpc current) ;; 32 | esac 33 | done 34 | echo "" 35 | } 36 | 37 | event= 38 | while :; do 39 | print_info 40 | event=$(mpc idle) 41 | done 42 | } 43 | 44 | format=; spacer="|" 45 | prev_s="" 46 | next_s="" 47 | play_s="" 48 | pause_s="" 49 | while getopts ":f:s:p:n:P:S:" opt; do 50 | case $opt in 51 | p) prev_s="$OPTARG" ;; 52 | n) next_s="$OPTARG" ;; 53 | P) play_s="$OPTARG" ;; 54 | S) play_s="$OPTARG" ;; 55 | f) format="$OPTARG" ;; 56 | s) spacer="$OPTARG" ;; 57 | \?) 58 | echo "Invalid option: -$OPTARG" 1>&2 59 | usage 60 | exit 1 61 | ;; 62 | :) 63 | echo "Option -$OPTARG requires an argument." 1>&2 64 | usage 65 | exit 1 66 | ;; 67 | esac 68 | done 69 | 70 | backend_mpc 71 | 72 | -------------------------------------------------------------------------------- /block/disk: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() { 4 | echo "Usage: $0 [-h] [-H] [-n SLEEP] [-t size|used|available|percent] DISK" 5 | echo "Output info on the specified disk." 6 | echo "" 7 | 8 | echo "Arguments" 9 | echo " -n " "Update interval." 10 | echo " -H " "Human-readable output." 11 | echo " -t " "Output type. One of: size, used, available, percent." 12 | echo " -f " "Number of floating points for human-readable output." 13 | echo "" 14 | } 15 | 16 | check_err() { 17 | # check if $dev exists 18 | if [ -z "$(df | awk "\$6 == \"$dev\" { print \$6 }")" ]; then 19 | echo "No disk $dev found" 1>&2 20 | exit 1 21 | 22 | # make sure $type is correct 23 | elif ! echo "$type" | grep -qE 'size|used|available|percent'; then 24 | echo "Type $type is invalid. Check -h for more info" 1>&2 25 | exit 1 26 | fi 27 | } 28 | 29 | run() { 30 | print_info() { 31 | df -P | awk -vprecision="$precision" -vdev="$dev" -vtype="$type" -vh="$human" \ 32 | ' 33 | function put(s) { 34 | if (h) 35 | if (s > 1024^2) 36 | printf "%." precision "f%s\n", s/1024^2, "G" 37 | else if (s > 1024^1) 38 | printf "%."precision"f%s\n", s/1024^1, "M" 39 | else 40 | printf "%."precision"f%s\n", s/1024^1, "K" 41 | else { 42 | print s 43 | } 44 | } 45 | 46 | # TODO fix this 47 | $6 == dev { 48 | if (type == "percent") print($5) 49 | else if (type == "size") put($2) 50 | else if (type == "used") put($3) 51 | else if (type == "available") put($4) 52 | } 53 | ' 54 | } 55 | 56 | while :; do 57 | print_info 58 | sleep $sleep || break 59 | wait $! 60 | done 61 | } 62 | 63 | sleep=300 64 | human=0 # false 65 | type=percent 66 | precision=2 67 | while getopts n:Ht:f:h arg; do 68 | case "$arg" in 69 | n) sleep="$OPTARG" ;; 70 | H) human=1 ;; 71 | t) type="$OPTARG" ;; 72 | f) precision="$OPTARG" ;; 73 | h) usage 74 | exit 0 75 | ;; 76 | ?) usage 77 | exit 1 78 | ;; 79 | esac 80 | done 81 | 82 | shift $((OPTIND - 1)) 83 | dev="${@-'/'}" 84 | 85 | check_err 86 | run 87 | -------------------------------------------------------------------------------- /assets/examplebar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## javyre colorscheme 4 | fg_color="#d8dee9" 5 | bg_color="#262626" 6 | bz_color="#151515" 7 | ac_color="#0d61ac" 8 | ur_color="#870000" 9 | 10 | rslant="" 11 | lslant="" 12 | 13 | lvl0="%{B$bz_color}%{F$bg_color}" 14 | lvl1="%{B$bg_color}%{F$fg_color}" 15 | lvl2="%{B$ac_color}%{F$fg_color}" 16 | urge="%{B$ur_color}%{F$fg_color}" 17 | 18 | trans() { 19 | ret= 20 | if [ "$4" = "l" ]; then 21 | _from="$3"; _to="$1" 22 | _slant="$lslant" 23 | else 24 | _from="$1"; _to="$3" 25 | _slant="$rslant" 26 | fi 27 | 28 | case $_from in 29 | 0) _fg="%{F$bz_color}" ;; 30 | 1) _fg="%{F$bg_color}" ;; 31 | 2) _fg="%{F$ac_color}" ;; 32 | u) _fg="%{F$ur_color}" ;; 33 | esac 34 | case $_to in 35 | 0) _bg="%{B$bz_color}$ret" ;; 36 | 1) _bg="%{B$bg_color}$ret" ;; 37 | 2) _bg="%{B$ac_color}$ret" ;; 38 | u) _bg="%{B$ur_color}$ret" ;; 39 | esac 40 | 41 | eval "ret=\"$_fg$_bg$_slant\$lvl$3\"" 42 | 43 | echo -n "$ret" 44 | } 45 | 46 | 47 | sep=" " 48 | 49 | screen1="DVI-1" 50 | screen2="DVI-0" 51 | 52 | i3ws_common="\ 53 | -S -B -c \ 54 | -f '$lvl0 ' ' ' \ 55 | -F '$lvl1 ' ' ' \ 56 | -u '$urge ' ' ' \ 57 | -psf '' \"$(trans 0 to 0)\" \ 58 | -psF '' \"$(trans 1 to 0)\" \ 59 | -psu '' \"$(trans u to 0)\" \ 60 | " 61 | 62 | lemonbar_common=$( 63 | # -u 2 -a 20 -b \ 64 | echo "\ 65 | -u 2 -a 20 \ 66 | -o '0' -f 'Noto Sans-12' \ 67 | -o '-1' -f 'Monaco for Powerline for Slantline-13' \ 68 | -o '-1' -f 'FontAwesome-12' \ 69 | " 70 | ) 71 | 72 | 73 | { 74 | echo -n "$lvl0" 75 | 76 | ### Left 77 | echo -n "%{l}" 78 | 79 | echo -n "<<" 80 | echo -n "i3ws " 81 | echo -n "-m $screen1 " 82 | echo -n "$i3ws_common" 83 | echo -n ">>" 84 | 85 | echo -n "$sep" 86 | trans 0 to 2 87 | 88 | 89 | echo -n "<>" 90 | echo -n " " 91 | echo -n "<>" 92 | 93 | trans 2 to 0 94 | 95 | 96 | ### Center 97 | echo -n "%{c}" 98 | 99 | trans 0 to 1 100 | echo -n "<>" 101 | trans 1 to 0 102 | 103 | echo -n "$sep" 104 | 105 | trans 0 to 2 106 | echo -n " " 107 | trans 2 to 1 108 | echo -n " / " 109 | trans 1 to 2 110 | echo -n ": <>" 111 | trans 2 to 0 112 | 113 | ### Right 114 | echo -n "%{r}" 115 | 116 | trans 0 to 1 l 117 | echo -n " <> " 118 | trans 1 to 2 l 119 | echo -n "  <> " 120 | 121 | echo -n "$lvl0" 122 | 123 | # Requires a patched lemonbar for xft support 124 | } | genbar -b '<<' -e '>>' | \ 125 | eval lemonbar $lemonbar_common "$screen1" | sh &> /dev/null & 126 | 127 | wait 128 | -------------------------------------------------------------------------------- /block/vol: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() { 4 | echo "Usage: $0 [-j < -i | -o > <-c CHANNEL>] [-s] [-i INTERVAL]" 5 | echo "Block for printing volume" 6 | echo "" 7 | 8 | echo "Arguments" 9 | echo " -h " "Print this help." 10 | echo " -j " "User jamyxer mode." 11 | echo " -c " "Channel to use for volume backend." 12 | echo " -s " "Add lemonbar formatted buttons for scrolling." 13 | echo " -i " "Interval for volume change. Default is 5." 14 | echo "" 15 | 16 | echo "For alsa mode, the script responds to SIGUSR1 to update volume." 17 | } 18 | 19 | jamyxer_mode() { 20 | print_formatted() { 21 | # Scroll action formatted for lemonbar 22 | if [ $scroll ]; then 23 | pre="%{A4:jmctl v$o\m $chan +$interval:}" 24 | pre="${pre}%{A5:jmctl v$o\m $chan -$interval:}" 25 | suf="%{A}%{A}" 26 | fi 27 | echo -n "$pre" 28 | echo $1 | awk '{ printf("%d", int( $1 )); }' 29 | echo "$suf" 30 | } 31 | 32 | if [ -z "$chan" ]; then 33 | echo "Channel not set!" 1>&2 34 | usage; exit 1 35 | fi 36 | 37 | if [ $jin ]; then o=o; else o=i; fi 38 | 39 | print_formatted $(jmctl v$o\g $chan 2>&1) 40 | while :; do 41 | print_formatted $(jmctl v$o\listn $chan 2>&1) 42 | done 43 | } 44 | 45 | alsa_mode() { 46 | print_vol() { 47 | # Command for retrieving volume 48 | cmd="amixer get Master \ 49 | | grep -Eo '\[[0-9]{1,3}%\]' \ 50 | | tr -d '[]%' \ 51 | | tail -1" 52 | 53 | if [ $scroll ]; then 54 | pre="%{A4:amixer set $chan $interval%+}" 55 | pre="${pre}%{A5:amixer set $chan $interval%-}" 56 | suf="%{A}%{A}" 57 | fi 58 | 59 | echo "${pre}$(eval $cmd)${suf}" 60 | } 61 | 62 | if [ "$1" = "update" ]; then 63 | print_vol 64 | return 65 | fi 66 | 67 | # Stay alive 68 | while :; do 69 | print_vol 70 | sleep 10 & 71 | wait $! 72 | done 73 | } 74 | 75 | mode=alsa # one of: jamyxer, alsa 76 | chan=Master 77 | interval=5 78 | while getopts ":jioc:su:" opt; do 79 | case $opt in 80 | j) mode=jamyxer ;; 81 | i) jin=1 ;; 82 | o) jin=0 ;; 83 | c) chan="$OPTARG" ;; 84 | s) scroll=1 ;; # scroll action meant for use with lemonbar 85 | i) interval="$OPTARG" ;; 86 | h) 87 | usage 88 | exit 0 89 | ;; 90 | \?) 91 | echo "Invalid option: -$OPTARG" 1>&2 92 | usage 93 | exit 1 94 | ;; 95 | :) 96 | echo "Option -$OPTARG requires an argument." 1>&2 97 | usage 98 | exit 1 99 | ;; 100 | esac 101 | done 102 | 103 | case "$mode" in 104 | jamyxer) 105 | jamyxer_mode 106 | ;; 107 | alsa) 108 | trap "alsa_mode update" USR1 109 | alsa_mode 110 | ;; 111 | *) 112 | echo "Invalid mode $mode, check -h for mode info." 1>&2 113 | exit 1 114 | ;; 115 | esac 116 | -------------------------------------------------------------------------------- /block/i3ws: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import i3ipc 5 | import re 6 | 7 | 8 | def get_args(): 9 | """Parse arguments.""" 10 | p = argparse.ArgumentParser(description='Output i3 workspace data ' 11 | + 'with each block ' 12 | + 'prefixed and suffixed ' 13 | + 'by specific identifiers ' 14 | + 'according to type') 15 | 16 | p.add_argument('-m', action='store', dest='monitor', 17 | help='Monitor to show workspaces from') 18 | 19 | p.add_argument('-c', action='store_true', dest='scroll', 20 | help='Add lemonbar formatted scroll actions') 21 | 22 | p.add_argument('-S', action='store_true', dest='strip', 23 | help='Strip workspace numbers') 24 | 25 | p.add_argument('-B', action='store_true', dest='butt', 26 | help='Add lemonbar formatted buttons') 27 | 28 | p.add_argument('-s', dest='sep', default='', 29 | help='Separator') 30 | 31 | p.add_argument('-f', dest='unf', default=['', ''], 32 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 33 | help='Unfocused block') 34 | 35 | p.add_argument('-F', dest='foc', default=['', ''], 36 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 37 | help='Focused block') 38 | 39 | p.add_argument('-u', dest='urg', default=['', ''], 40 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 41 | help='Urgent block') 42 | 43 | p.add_argument('-psf', dest='unf_ps', default=['', ''], 44 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 45 | help='Global p/s for unfocused block in beginning') 46 | 47 | p.add_argument('-psF', dest='foc_ps', default=['', ''], 48 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 49 | help='Global p/s for focused block in beginning') 50 | 51 | p.add_argument('-psu', dest='urg_ps', default=['', ''], 52 | nargs=2, metavar=('PREFIX', 'SUFFIX'), 53 | help='Global p/s for urgent block in beginning') 54 | 55 | return p.parse_args() 56 | 57 | 58 | def strip_ws(s): 59 | """Strip workspace numbers. 60 | 61 | Remove the number and the colon. 62 | """ 63 | 64 | return s[s.find(':') + 1:] 65 | 66 | 67 | def prepend_chars(s, char, prep): 68 | """Prepend every instance of a character. 69 | 70 | In s, prepend every instance of char with prep. 71 | """ 72 | 73 | return re.sub(char, lambda m: prep + m.group(0), s) 74 | 75 | 76 | def print_workspaces(): 77 | """Generate the workspace numbers.""" 78 | 79 | a = [] # print 80 | 81 | # Filter through workspaces in selected monitor 82 | ws = i3.get_workspaces() 83 | if arg.monitor is not None: 84 | ws = [w for w in ws if w.output == arg.monitor] 85 | 86 | # Iterate through workspaces 87 | for i, w in enumerate(ws): 88 | # Strip workspace numbers if necessary 89 | if arg.strip: 90 | n = strip_ws(w.name) 91 | else: 92 | n = w.name 93 | 94 | # When a window is focused the workspace isn't 95 | # So we check if the workspace contains a focused container 96 | if w.focused: 97 | pair = arg.foc 98 | ps = arg.foc_ps 99 | elif w.urgent: 100 | pair = arg.urg 101 | ps = arg.urg_ps 102 | else: 103 | pair = arg.unf 104 | ps = arg.unf_ps 105 | 106 | p = ps[0] if i == 0 else '' 107 | s = ps[1] if i == len(ws)-1 else '' 108 | 109 | a.append(p + '{0[0]}{1}{0[1]}'.format(pair, n) + s) 110 | 111 | # Add lemonbar button commands if necessary 112 | if arg.butt: 113 | a[-1] = '%%{A:i3-msg workspace %s:}%s%%{A}' % \ 114 | (prepend_chars(w.name, ':', '\\'), a[-1]) 115 | 116 | if arg.scroll: 117 | if (w.visible and arg.monitor is not None) or\ 118 | (w.focused and arg.monitor is None): 119 | next = list(ws)[min(i+1, len(list(ws))-1)].name 120 | prev = list(ws)[max(i-1, 0)].name 121 | 122 | # Flush is necessary for piping 123 | if arg.scroll: 124 | print('%%{A5:i3-msg workspace %s:}' % 125 | prepend_chars(next, ':', '\\'), end='') 126 | print('%%{A4:i3-msg workspace %s:}' % 127 | prepend_chars(prev, ':', '\\'), end='') 128 | print(arg.sep.join(a) + ('%{A}'*2 if arg.scroll else ''), flush=True) 129 | 130 | 131 | def on_event(self, e): 132 | """Called on every workspace focus.""" 133 | 134 | print_workspaces() 135 | 136 | 137 | if __name__ == '__main__': 138 | 139 | # Arguments 140 | arg = get_args() 141 | 142 | # Connect to i3 143 | i3 = i3ipc.Connection() 144 | 145 | # Initial workspaces print 146 | print_workspaces() 147 | 148 | # Subscribe to events and start main loop 149 | i3.on('workspace::', on_event) 150 | i3.main() 151 | -------------------------------------------------------------------------------- /genbar: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Generate a bar from stdin 4 | 5 | This script asynchronously runs each command specified in stdin and 6 | replaces each command with its respective output and outputs it on 7 | every newline. 8 | 9 | Commands are specified using the <> format. Each must continuously 10 | run and output every few seconds. Commands must flush their buffer. 11 | """ 12 | 13 | from subprocess import Popen, PIPE 14 | from threading import Thread 15 | 16 | import re 17 | import shlex 18 | import signal 19 | import sys 20 | import os 21 | 22 | import argparse 23 | 24 | try: 25 | ENV_PATH = os.environ.copy() 26 | ENV_PATH['PATH'] = ENV_PATH['TENDERBLOCKS'] + ':' + ENV_PATH['PATH'] 27 | except KeyError: 28 | ENV_PATH = os.environ 29 | 30 | 31 | class LineGen(object): 32 | """ Generate line with asynchronous outputs of commands """ 33 | def __init__(self, base, delims, settings): 34 | # mini util function for recording m and returning '{}' 35 | def repl(m): 36 | self.cmds.append(m.group(1)) 37 | return '{}' 38 | 39 | # set args to corresponding attributes 40 | self.LDELIM, self.RDELIM = delims[0], delims[1] 41 | self.base = base 42 | 43 | # for extra settings 44 | self.settings = settings 45 | 46 | # empty default args list 47 | self.cmds = [] 48 | 49 | # escape all '{' and '}' characters 50 | self.template = re.sub(r'[{}]', lambda m: m.group()*2, self.base) 51 | 52 | # replace all cmds with '{}' and record them to cmds list 53 | # NOTE: SIDE EFFECT: builds cmds list 54 | self.template = re.sub( 55 | self.LDELIM + r'([^'+self.RDELIM+']*)' + self.RDELIM, 56 | repl, self.template) 57 | 58 | # unescape '{' & '}' in cmds 59 | self.cmds = [re.sub(r'{{', '{', c) for c in self.cmds] 60 | self.cmds = [re.sub(r'}}', '}', c) for c in self.cmds] 61 | 62 | # initialize lists with null values 63 | self.procs = [None]*len(self.cmds) 64 | self.outs = ['']*len(self.cmds) 65 | 66 | # handle interupt signals (ignoring given params) 67 | signal.signal(signal.SIGINT, lambda *_: self.stop()) 68 | signal.signal(signal.SIGTERM, lambda *_: self.stop()) 69 | signal.signal(signal.SIGPIPE, lambda *_: self.stop()) 70 | 71 | def start(self): 72 | """ Start processes """ 73 | 74 | # mini util function for assigning processes to threads 75 | def assig_p(id): 76 | # macro for Popen (gets repetitive without this) 77 | def openP(): return Popen(curcmd, stdin=lastout, stdout=PIPE, 78 | universal_newlines=True, env=ENV_PATH, 79 | shell=False) 80 | 81 | # start process 82 | 83 | # Apparently a lot of people absolutely hate the use of 84 | # `shell=True` so we parse pipes ourselves here 85 | split = shlex.split(self.cmds[id]) 86 | curcmd = [] 87 | lastout = None 88 | # iterate through elements of the input command 89 | for i, a in enumerate(split): 90 | # intermediate commands get piped into eachother 91 | if a == '|': 92 | lastout = openP().stdout 93 | curcmd = [] 94 | continue 95 | else: 96 | curcmd.append(a) 97 | 98 | # if it's the last command 99 | if i == len(split)-1: 100 | self.procs[id] = openP() 101 | 102 | # wait for new stdout line from command output 103 | for line in self.procs[id].stdout: 104 | self.outs[id] = line if line[-1] != '\n' else line[:-1] 105 | print(self.template.format(*self.outs), flush=True) 106 | 107 | # assign each cmd process to a thread 108 | for i in range(len(self.cmds)): 109 | Thread(target=assig_p, args=(i,)).start() 110 | 111 | def stop(self): 112 | """ Terminate each process """ 113 | print('Stopping...', file=sys.stderr) 114 | for i, p in enumerate(self.procs): 115 | print(' - Stopping proc %s: \'%s\'' 116 | % (i, ' '.join(p.args)), file=sys.stderr) 117 | p.stdout.close() 118 | # NOTE: We technically dont need terminate 119 | # because stdout.close sends SIGPIPE already 120 | p.terminate() 121 | 122 | print('Done', [p.poll() for p in self.procs], file=sys.stderr) 123 | 124 | 125 | if __name__ == '__main__': 126 | # parse args 127 | parser = argparse.ArgumentParser(description='Generate string to stdout' 128 | ' from asynchronous command outputs.' 129 | ' Replace the string between the begin' 130 | ' delimiter and end delimiter with the' 131 | ' command it contains\n' 132 | 'Default delimiters are << and >>' 133 | ' respectively.' 134 | ) 135 | parser.add_argument('-b', action='store', dest='begin', default='<<') 136 | parser.add_argument('-e', action='store', dest='end', default='>>') 137 | 138 | settings = parser.parse_args() 139 | # args parsed 140 | 141 | # create linegen object 142 | linegen = LineGen(sys.stdin.read(), 143 | (settings.begin, settings.end), settings) 144 | 145 | # start linegen processes 146 | linegen.start() 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bartender 2 | 3 | ![logo](assets/logo_light_centered.png) 4 | *** 5 | 6 |

7 | Usage 8 | | 9 | Installation 10 | | 11 | Blocks 12 |

13 | 14 | *** 15 | Simple, modular bar generator 16 | Designed to be used with `lemonbar` 17 | 18 | ## Usage 19 | ![Usage](assets/Usage.png) 20 | 21 | ![Blocks](assets/Blocks.png) 22 | *many more blocks to come... (contributions are very welcome)* 23 | 24 | ### Configuration 25 | 26 | Bars can easily be generated using a shell script. The script must have the 27 | genbar command and the commands under block in its path. Alternatively, the 28 | `$TENDERBLOCKS` variable can be set to the blocks directory for genbar to 29 | prepend to the path. 30 | 31 | Next, the string for `lemonbar` is piped into `genbar`, where each 32 | command to be run is delimited by `<<` and `>>`, which can be changed. 33 | 34 | Finally, the output of `genbar` is piped into `lemonbar`. Each step can 35 | be seen in the example script below. `lemonbar`'s output can also be 36 | piped to sh for buttons. 37 | 38 | ### Example 39 | 40 | ````sh 41 | #!/bin/sh 42 | 43 | # Optional env variable used by genbar to prepend to the path 44 | export TENDERBLOCKS="path/to/bartender/block" 45 | 46 | # Between brackets the string to be piped is generated 47 | # This allows us to break down the string into readable segments 48 | # -n is used to not have a newline. Any command can be used, such as printf 49 | 50 | # Make sure genbar is in the PATH 51 | { 52 | 53 | # Center 54 | echo -n "%{c}" 55 | echo -n "<>" 56 | 57 | } | genbar | lemonbar | sh 58 | ```` 59 | 60 | 61 | ## Installation 62 | 63 | ### Manual Installation 64 | 65 | Bartender can simply be installed by cloning the repo and adding the root 66 | directory to PATH. For the blocks, any script can be used. `genbar` allows the 67 | usage of a `$TENDERBLOCKS` variable, which is prepended to the PATH. This can 68 | be used to point to the `block` directory. 69 | 70 | ``` 71 | $ git clone https://github.com/Snowlabs/bartender ~/.local/share/Bartender 72 | $ mkdir -p ~/bin 73 | $ ln -sT ~/.local/share/Bartender/genbar ~/bin/genbar 74 | $ chmod +x ~/bin/genbar 75 | # make sure bin ~/bin is in your PATH 76 | ``` 77 | 78 | #### ~/.bashrc or ~/.zshrc 79 | ```bash 80 | export TENDERBLOCKS="~/.local/share/bartender/block" 81 | ``` 82 | 83 | ### Recommended Installation 84 | 85 | We recommend using [zplug](github.com/zplug/zplug) for an easy installation: 86 | (Don't forget to set the path to the tender blocks!) 87 | #### ~/.zshrc 88 | ```zsh 89 | export TENDERBLOCKS="/repos/Snowlabs/bartender/block" 90 | 91 | zplug "Snowlabs/bartender", use:"genbar", as:command 92 | ``` 93 | *`` is `~/.zplug` by default* 94 | 95 | ## Blocks 96 | :bomb:: Event-based 97 | 98 | :hourglass:: Not event-based 99 | 100 | - :hourglass: clock 101 | - :hourglass: disk 102 | - :bomb: i3window 103 | - :bomb: i3ws 104 | - :bomb: music 105 | - :bomb: vol 106 | 107 | 108 | ### clock 109 | clock 110 | 111 | Prints the current time. Uses the same format as the `date` command. 112 | 113 | | Option | Description | Default | 114 | | :----: | ----------- | :-----: | 115 | | `-n` | Update interval for the clock | `5` | 116 | | | Format used by the `date` command | | 117 | 118 | ### disk 119 | disk 120 | 121 | | Option | Description | Default | 122 | | :----: | ----------- | :-----: | 123 | | `-n` | Update inverval | `300` | 124 | | `-H` | Make output human readable | `false` | 125 | | `-t` | Type of info to output: size, used available, used% | percent | 126 | | `-f` | Floating point precision for human-readable output | `2` | 127 | | | Mountpoint of the partition | `/` | 128 | 129 | Print info about the specified partition. Can print the disk size, used space, 130 | available space and the used percentage. 131 | 132 | ### i3window 133 | i3window 134 | 135 | For i3wm, print the focused window. 136 | 137 | Event-based 138 | 139 | **Deps**: `python3` 140 | **PyPI modules**: `i3ipc` 141 | 142 | ### i3ws 143 | i3ws 144 | 145 | | Option | Description | Default | 146 | | :----: | ----------- | :-----: | 147 | | `-m` | Which monitor to show workspaces from | All | 148 | | `-c` | Add lemonbar formatted scroll actions | | 149 | | `-S` | Strip workspace numbers | | 150 | | `-B` | Add lemonbar formatted numbers for changing workspace | | 151 | | `-s` | Separator | | 152 | | `-f` | Prefix and suffix for unfocused workspace | | 153 | | `-F` | Prefix and suffix for focused workspace | | 154 | | `-u` | Prefix and suffix for urgent workspace | | 155 | 156 | Print the workspaces under i3wm. 157 | 158 | Event-based 159 | 160 | **Deps**: `python3` 161 | **PyPI modules**: `i3ipc` 162 | 163 | ### music 164 | music 165 | 166 | Default button arguments use [fontawesome](http://fontawesome.io/). 167 | 168 | | Option | Description | Default | 169 | | :----: | ----------- | :-----: | 170 | | `-f` | Specify the format of the output | | 171 | | `-s` | Used with `-f`. Specify the spacer string | | 172 | | `-p` | String for the previous button | | 173 | | `-n` | String for the next button | | 174 | | `-P` | String for the play button | | 175 | | `-S` | String for the payse button | | 176 | 177 | Print information about music, using various backends. Current supports only 178 | `mpd`-based servers through `mpc`. 179 | 180 | Event-based with `mpc` 181 | 182 | **Optional deps**: `mpc` 183 | 184 | ### vol 185 | vol 186 | 187 | | Option | Description | Default | 188 | | :----: | ----------- | :-----: | 189 | | `-j` | Use the jamyxer backend | | 190 | | `-c` | Audio channel to use | | 191 | | `-s` | Add lemonbar scroll buttons to change volume | | 192 | | `-i` | Interval for changing volume (step) | | 193 | 194 | Print volume information, for various backends. Currently supports: ALSA, 195 | [jamyxer](https://github.com/Javyre/jamyxer) 196 | 197 | Event-based with the jamyxer backend 198 | 199 | **Optional deps**: `alsa-utils` or `jmctl` (from jamyxer) 200 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 64 | 72 | 80 | 89 | 94 | Bartender 105 | 107 | 110 | 113 | 119 | 120 | 123 | 124 | 127 | 130 | 136 | 137 | 140 | 141 | 142 | 143 | 152 | 157 | Bartender 168 | 171 | 175 | 179 | 185 | 186 | 190 | 191 | 195 | 199 | 205 | 206 | 210 | 211 | 212 | 213 | 222 | 231 | 236 | Bartender 247 | 251 | 255 | 259 | 265 | 266 | 270 | 271 | 275 | 279 | 285 | 286 | 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /assets/Usage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 51 | 53 | 54 | 56 | image/svg+xml 57 | 59 | 60 | 61 | 62 | 63 | 68 | 77 | 78 | 83 | $ { echo -n "Time: %{c}<<clock>>" } | genbar | lemonbar | sh 110 | 116 | 120 | 124 | 130 | 131 | 135 | 136 | 140 | 144 | 150 | 151 | 155 | 156 | 157 | 166 | 176 | 185 | 204 | 214 | 224 | 234 | 254 | Tender Blocks 267 | Runs the clock block. 280 | 290 | Lemonbar Format 303 | Centers the string 316 | 326 | 336 | 346 | 356 | 366 | Button Events 379 | Used for executing lemonbar buttons 397 | 407 | 427 | 428 | 429 | -------------------------------------------------------------------------------- /assets/Blocks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 60 | 65 | 70 | 75 | 80 | 85 | 92 | 93 | 95 | 96 | 98 | image/svg+xml 99 | 101 | 102 | 103 | 104 | 105 | 111 | 118 | 119 | 124 | Blocks 135 | 148 | 154 | 159 | 160 | 166 | 172 | 2 183 | 4 194 | 10 205 | 211 | i3ws 222 | 228 | Terminal 239 | 245 | i3window 256 | 262 | Sun, 10 Sep 03:14 273 | 279 | clock 290 | 296 | || Boston- Last Day of School 307 | 313 | music 324 | 330 |  98 341 | 347 | vol 358 | 364 | 375 | 381 | / 392 | : 31% 403 | 409 | disk 420 | 421 | 422 | -------------------------------------------------------------------------------- /assets/Logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 32 | 36 | 37 | 38 | 64 | 66 | 67 | 69 | image/svg+xml 70 | 72 | 73 | 74 | 75 | 76 | 81 | 90 | 99 | 100 | 105 | 110 | Bartender 121 | 125 | 129 | 133 | 139 | 140 | 144 | 145 | 149 | 153 | 159 | 160 | 164 | 165 | 166 | 167 | 174 | 181 | 188 | 195 | 202 | 210 | 217 | 225 | 233 | 239 | 247 | 253 | 261 | 269 | 275 | 283 | 289 | 295 | 301 | 306 | 314 | 319 | 324 | 332 | 337 | 345 | 351 | 357 | 363 | 369 | 375 | 384 | 389 | 398 | 404 | 409 | 415 | 418 | 424 | 430 | 436 | 437 | 440 | 446 | 452 | 458 | 459 | 462 | 468 | 474 | 480 | 481 | 484 | 487 | 493 | 499 | 505 | 506 | 508 | 517 | 526 | 535 | 536 | 537 | 541 | 545 | 551 | 557 | 563 | 564 | 567 | 576 | 585 | 594 | 595 | 596 | 599 | 602 | 606 | 612 | 618 | 624 | 625 | 628 | 637 | 646 | 655 | 656 | 657 | 658 | wlabs 669 | n 680 | 684 | 687 | 693 | 699 | 705 | 711 | 712 | 718 | 719 | 725 | 726 | 730 | 736 | 742 | 748 | 749 | 753 | 762 | 771 | 780 | 781 | 790 | 796 | 799 | 805 | 811 | 817 | 823 | 824 | 830 | 831 | 837 | 838 | 845 | 849 | 852 | 858 | 864 | 870 | 876 | 877 | 883 | 884 | 890 | 891 | 898 | 902 | 905 | 911 | 917 | 923 | 929 | 930 | 936 | 937 | 943 | 944 | 951 | 955 | 958 | 964 | 970 | 976 | 982 | 983 | 989 | 990 | 996 | 997 | 998 | 999 | --------------------------------------------------------------------------------