]> danix's work - my-dotfiles.git/commitdiff
major update and addition of various config files. Reorganized a few directories
authordanix <redacted>
Wed, 6 Nov 2024 11:54:24 +0000 (12:54 +0100)
committerdanix <redacted>
Wed, 6 Nov 2024 11:54:24 +0000 (12:54 +0100)
183 files changed:
etc/bash_completion.d/sbopkg.bash [new file with mode: 0644]
home-bin/betterlockscreen [new file with mode: 0644]
home-bin/blackpearl-appsmenu.sh [new file with mode: 0644]
home-bin/blackpearl-emoji.sh [new file with mode: 0644]
home-bin/blackpearl-notes.sh [new file with mode: 0644]
home-bin/blackpearl-powermenu.sh [new file with mode: 0644]
home-bin/blackpearl-runner.sh [new file with mode: 0644]
home-bin/blackpearl-scrotmenu.sh [new file with mode: 0644]
home-bin/blackpearl-sshmenu.sh [new file with mode: 0644]
home-bin/blackpearl-symbols.sh [new file with mode: 0644]
home-bin/blackpearl-utilsmenu.sh [new file with mode: 0644]
home-bin/blackpearl-window.sh [new file with mode: 0644]
home-bin/change_wallpaper.sh [new file with mode: 0644]
home-bin/dunst-snooze.sh [new file with mode: 0644]
home-bin/dunst_vol_brig.sh [new file with mode: 0644]
home-bin/feh-blur [new file with mode: 0644]
home-bin/gify.sh [new file with mode: 0644]
home-bin/hideIt.sh [new file with mode: 0644]
home-bin/hidebars.sh [new file with mode: 0644]
home-bin/i3-exit [new file with mode: 0644]
home-bin/i3-scrot [new file with mode: 0644]
home-bin/i3lock-blur [new file with mode: 0644]
home-bin/i3lock-fancy [new file with mode: 0644]
home-bin/i3suspend [new file with mode: 0644]
home-bin/info-airqualityindex.sh [new file with mode: 0644]
home-bin/is_installed [new file with mode: 0644]
home-bin/lightsOn.sh [new file with mode: 0644]
home-bin/maiumin [new file with mode: 0644]
home-bin/mkpass [new file with mode: 0644]
home-bin/mvb [new file with mode: 0644]
home-bin/my_netstat [new file with mode: 0644]
home-bin/nopy.sh [new file with mode: 0644]
home-bin/nospaces [new file with mode: 0644]
home-bin/notes [new file with mode: 0644]
home-bin/polybar-kdeconnect.sh [new file with mode: 0644]
home-bin/qwalwal.sh [new file with mode: 0644]
home-bin/run-polybar.sh [new file with mode: 0644]
home-bin/send-to-device.sh [new file with mode: 0644]
home-bin/slack-updates [new file with mode: 0644]
home-bin/speedtest [new file with mode: 0644]
home-bin/superenalotto.sh [new file with mode: 0644]
home-bin/symbols.sh [new file with mode: 0644]
home-bin/vt-color-scheme.py [new file with mode: 0644]
home-bin/wacom [new file with mode: 0644]
home-bin/wal.sh [new file with mode: 0644]
home-bin/wallpaper.sh [new file with mode: 0644]
home-bin/walogram [new file with mode: 0644]
home-bin/walwal.sh [new file with mode: 0644]
home-config/i3/config [new file with mode: 0644]
home-config/picom/picom.conf [new file with mode: 0644]
home-config/polybar/config [new file with mode: 0644]
home-config/polybar/config.ini-original [new file with mode: 0644]
home-config/polybar/config.ini-vecchio [new file with mode: 0644]
home-config/polybar/config.ini.boh [new file with mode: 0644]
home-config/polybar/modules/airquality.ini [new file with mode: 0644]
home-config/polybar/modules/battery.ini [new file with mode: 0644]
home-config/polybar/modules/cpu.ini [new file with mode: 0644]
home-config/polybar/modules/date.ini [new file with mode: 0644]
home-config/polybar/modules/duckstation.ini [new file with mode: 0644]
home-config/polybar/modules/dunst-snooze.ini [new file with mode: 0644]
home-config/polybar/modules/filesys.ini [new file with mode: 0644]
home-config/polybar/modules/gmail-0.ini [new file with mode: 0644]
home-config/polybar/modules/gmail-1.ini [new file with mode: 0644]
home-config/polybar/modules/gmail-2.ini [new file with mode: 0644]
home-config/polybar/modules/gmail/.gitignore [new file with mode: 0644]
home-config/polybar/modules/gmail/LICENSE [new file with mode: 0644]
home-config/polybar/modules/gmail/README.md [new file with mode: 0644]
home-config/polybar/modules/gmail/auth.py [new file with mode: 0755]
home-config/polybar/modules/gmail/client_secrets.json [new file with mode: 0644]
home-config/polybar/modules/gmail/credentials_65d85.json [new file with mode: 0644]
home-config/polybar/modules/gmail/credentials_danixland.json [new file with mode: 0644]
home-config/polybar/modules/gmail/credentials_itdm.json [new file with mode: 0644]
home-config/polybar/modules/gmail/launch.py [new file with mode: 0755]
home-config/polybar/modules/gmail/list_labels.py [new file with mode: 0755]
home-config/polybar/modules/gmail/poetry.lock [new file with mode: 0644]
home-config/polybar/modules/gmail/preview.png [new file with mode: 0644]
home-config/polybar/modules/gmail/pyproject.toml [new file with mode: 0644]
home-config/polybar/modules/i3.ini [new file with mode: 0644]
home-config/polybar/modules/kdeconnect.ini [new file with mode: 0644]
home-config/polybar/modules/keyboard.ini [new file with mode: 0644]
home-config/polybar/modules/memory.ini [new file with mode: 0644]
home-config/polybar/modules/network.ini [new file with mode: 0644]
home-config/polybar/modules/pulseaudio.ini [new file with mode: 0644]
home-config/polybar/modules/slackware.ini [new file with mode: 0644]
home-config/polybar/modules/temperature.ini [new file with mode: 0644]
home-config/polybar/modules/tray.ini [new file with mode: 0644]
home-config/polybar/modules/weather.ini [new file with mode: 0644]
home-config/polybar/modules/weather/openweathermap-simple.sh [new file with mode: 0755]
home-config/polybar/modules/weather/openweathermap-simple.sh.old [new file with mode: 0755]
home-config/polybar/modules/windowlist.ini [new file with mode: 0644]
home-config/polybar/modules/windows.ini [new file with mode: 0644]
home-config/polybar/modules/workspaces.ini [new file with mode: 0644]
home-config/polybar/scripts/windowlist/LICENSE [new file with mode: 0644]
home-config/polybar/scripts/windowlist/Makefile [new file with mode: 0644]
home-config/polybar/scripts/windowlist/README.md [new file with mode: 0644]
home-config/polybar/scripts/windowlist/click-actions/close [new file with mode: 0755]
home-config/polybar/scripts/windowlist/click-actions/close.c [new file with mode: 0644]
home-config/polybar/scripts/windowlist/click-actions/minimize [new file with mode: 0755]
home-config/polybar/scripts/windowlist/click-actions/minimize.c [new file with mode: 0644]
home-config/polybar/scripts/windowlist/click-actions/raise [new file with mode: 0755]
home-config/polybar/scripts/windowlist/click-actions/raise.c [new file with mode: 0644]
home-config/polybar/scripts/windowlist/config.toml [new file with mode: 0644]
home-config/polybar/scripts/windowlist/main [new file with mode: 0755]
home-config/polybar/scripts/windowlist/main.c [new file with mode: 0644]
home-config/polybar/scripts/windowlist/screenshot.png [new file with mode: 0644]
home-config/polybar/scripts/windowlist/toml-c.h [new file with mode: 0644]
home-config/polybar/scripts/windowlist/windowlist.c [new file with mode: 0644]
home-config/polybar/scripts/windowlist/windowlist.h [new file with mode: 0644]
home-config/polybar/scripts/windowlist/windowlist.o [new file with mode: 0644]
home-config/rofi/darknix/appmenu.rasi [new file with mode: 0644]
home-config/rofi/darknix/appslist.rasi [new file with mode: 0644]
home-config/rofi/darknix/i3exit.rasi [new file with mode: 0644]
home-config/rofi/darknix/libs/reset.rasi [new file with mode: 0644]
home-config/rofi/darknix/libs/settings.rasi [new file with mode: 0644]
home-config/rofi/darknix/main.rasi [new file with mode: 0644]
home-config/rofi/darknix/notes.rasi [new file with mode: 0644]
home-config/rofi/darknix/powermenu.rasi [new file with mode: 0644]
home-config/rofi/darknix/runner.rasi [new file with mode: 0644]
home-config/rofi/darknix/scrotmenu.rasi [new file with mode: 0644]
home-config/rofi/darknix/sshmenu.rasi [new file with mode: 0644]
home-config/rofi/darknix/utilsmenu.rasi [new file with mode: 0644]
home-config/rofi/rofi-symbols/1.math.symbols [new file with mode: 0644]
home-config/rofi/rofi-symbols/2.greek(distinct).symbols [new file with mode: 0644]
picom/picom.conf [new file with mode: 0644]
polybar/config [new file with mode: 0644]
polybar/config.ini-original [new file with mode: 0644]
polybar/config.ini-vecchio [new file with mode: 0644]
polybar/config.ini.boh [new file with mode: 0644]
polybar/modules/airquality.ini [new file with mode: 0644]
polybar/modules/battery.ini [new file with mode: 0644]
polybar/modules/cpu.ini [new file with mode: 0644]
polybar/modules/date.ini [new file with mode: 0644]
polybar/modules/duckstation.ini [new file with mode: 0644]
polybar/modules/dunst-snooze.ini [new file with mode: 0644]
polybar/modules/filesys.ini [new file with mode: 0644]
polybar/modules/gmail-0.ini [new file with mode: 0644]
polybar/modules/gmail-1.ini [new file with mode: 0644]
polybar/modules/gmail-2.ini [new file with mode: 0644]
polybar/modules/gmail/.gitignore [new file with mode: 0644]
polybar/modules/gmail/LICENSE [new file with mode: 0644]
polybar/modules/gmail/README.md [new file with mode: 0644]
polybar/modules/gmail/auth.py [new file with mode: 0755]
polybar/modules/gmail/client_secrets.json [new file with mode: 0644]
polybar/modules/gmail/credentials_65d85.json [new file with mode: 0644]
polybar/modules/gmail/credentials_danixland.json [new file with mode: 0644]
polybar/modules/gmail/credentials_itdm.json [new file with mode: 0644]
polybar/modules/gmail/launch.py [new file with mode: 0755]
polybar/modules/gmail/list_labels.py [new file with mode: 0755]
polybar/modules/gmail/poetry.lock [new file with mode: 0644]
polybar/modules/gmail/preview.png [new file with mode: 0644]
polybar/modules/gmail/pyproject.toml [new file with mode: 0644]
polybar/modules/i3.ini [new file with mode: 0644]
polybar/modules/kdeconnect.ini [new file with mode: 0644]
polybar/modules/keyboard.ini [new file with mode: 0644]
polybar/modules/memory.ini [new file with mode: 0644]
polybar/modules/network.ini [new file with mode: 0644]
polybar/modules/pulseaudio.ini [new file with mode: 0644]
polybar/modules/slackware.ini [new file with mode: 0644]
polybar/modules/temperature.ini [new file with mode: 0644]
polybar/modules/tray.ini [new file with mode: 0644]
polybar/modules/weather.ini [new file with mode: 0644]
polybar/modules/weather/openweathermap-simple.sh [new file with mode: 0755]
polybar/modules/weather/openweathermap-simple.sh.old [new file with mode: 0755]
polybar/modules/windowlist.ini [new file with mode: 0644]
polybar/modules/windows.ini [new file with mode: 0644]
polybar/modules/workspaces.ini [new file with mode: 0644]
polybar/scripts/windowlist/LICENSE [new file with mode: 0644]
polybar/scripts/windowlist/Makefile [new file with mode: 0644]
polybar/scripts/windowlist/README.md [new file with mode: 0644]
polybar/scripts/windowlist/click-actions/close [new file with mode: 0755]
polybar/scripts/windowlist/click-actions/close.c [new file with mode: 0644]
polybar/scripts/windowlist/click-actions/minimize [new file with mode: 0755]
polybar/scripts/windowlist/click-actions/minimize.c [new file with mode: 0644]
polybar/scripts/windowlist/click-actions/raise [new file with mode: 0755]
polybar/scripts/windowlist/click-actions/raise.c [new file with mode: 0644]
polybar/scripts/windowlist/config.toml [new file with mode: 0644]
polybar/scripts/windowlist/main [new file with mode: 0755]
polybar/scripts/windowlist/main.c [new file with mode: 0644]
polybar/scripts/windowlist/screenshot.png [new file with mode: 0644]
polybar/scripts/windowlist/toml-c.h [new file with mode: 0644]
polybar/scripts/windowlist/windowlist.c [new file with mode: 0644]
polybar/scripts/windowlist/windowlist.h [new file with mode: 0644]
polybar/scripts/windowlist/windowlist.o [new file with mode: 0644]

diff --git a/etc/bash_completion.d/sbopkg.bash b/etc/bash_completion.d/sbopkg.bash
new file mode 100644 (file)
index 0000000..8ec81ca
--- /dev/null
@@ -0,0 +1,84 @@
+# bash completion for sbopkg tool
+
+_sbopkg()
+{
+    COMPREPLY=()
+
+    local cur="${COMP_WORDS[COMP_CWORD]}"
+    local prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+    if [[ "$cur" == -* ]]; then
+        COMPREPLY=( $( compgen -W '-b -c -d -e -f -g -h -i -k -l \
+                      -o -P -p -q -R -r -s -u -V -v' -- "$cur" ) )
+        return 0
+    fi
+
+    case $prev in
+        -e)
+            COMPREPLY=( $( compgen -W 'ask continue stop' -- "$cur" ) )
+            return 0
+            ;;
+        -f)
+            COMPREPLY=( $( compgen -f -- "$cur" ) )
+            return 0
+            ;;
+        -d)
+            COMPREPLY=( $( compgen -d -- "$cur" ) )
+            return 0
+            ;;
+        -V)
+            COMPREPLY=( $( compgen -W "? \
+                $(sbopkg -V ? 2>&1 | cut -s -f1)" -- "$cur" ) )
+            return 0
+            ;;
+        -i|-b)
+            ;;
+        *)
+            return 0
+            ;;
+    esac
+
+    local config="/etc/sbopkg/sbopkg.conf"
+
+    for (( i=${#COMP_WORDS[@]}-1; i>0; i-- )); do
+        if [[ "${COMP_WORDS[i]}" == -f ]]; then
+            config="${COMP_WORDS[i+1]}"
+            break
+        fi
+    done
+
+    if [ ! -r "$config" ]; then
+        return 0
+    fi
+
+    . $config
+
+    for (( i=1; i<${#COMP_WORDS[@]}; i++ )); do
+        case "${COMP_WORDS[i]}" in
+            -V)
+                REPO_NAME="${COMP_WORDS[i+1]%%/*}"
+                REPO_BRANCH="${COMP_WORDS[i+1]#*/}"
+                ;;
+            -d)
+                REPO_ROOT="${COMP_WORDS[i+1]}"
+                ;;
+        esac
+    done
+
+    if [ -d "$QUEUEDIR" ]; then
+        local queues=($(cd $QUEUEDIR; compgen -f -X "!*.sqf" -- "$cur"))
+    fi
+
+    if [ -r "$REPO_ROOT/$REPO_NAME/$REPO_BRANCH/SLACKBUILDS.TXT" ]; then
+        COMPREPLY=($(sed -ne "/^SLACKBUILD NAME: $cur/{s/^SLACKBUILD NAME: //;p}" \
+                         $REPO_ROOT/$REPO_NAME/$REPO_BRANCH/SLACKBUILDS.TXT)
+                   ${queues[@]})
+    elif [ -d "$REPO_ROOT/$REPO_NAME/" ]; then
+        COMPREPLY=($(find $REPO_ROOT/$REPO_NAME \
+                          \! -path $REPO_ROOT/$REPO_NAME/'.git/*' \
+                          -mindepth 2 -maxdepth 2 \
+                          -type d -name $cur\* \
+                          -printf '%f\n')
+                   ${queues[@]})
+    fi
+} && complete -o filenames -F _sbopkg sbopkg
diff --git a/home-bin/betterlockscreen b/home-bin/betterlockscreen
new file mode 100644 (file)
index 0000000..0b92ad9
--- /dev/null
@@ -0,0 +1,942 @@
+#!/usr/bin/env bash
+
+# Author : Copyright (c) 2017-2023 Pavan Jadhaw, and others (https://github.com/betterlockscreen/betterlockscreen/graphs/contributors)
+# Project Repository : https://github.com/betterlockscreen/betterlockscreen
+
+cmd_exists () {
+    command -v "$1" >/dev/null
+}
+
+init_config () {
+    # default options
+    display_on=0
+    span_image=false
+    lock_timeout=300
+    fx_list=(dim blur dimblur pixel dimpixel color)
+    dim_level=40
+    blur_level=1
+    pixel_scale=10,1000
+    solid_color=333333
+    description=""
+    quiet=false
+    i3lockcolor_bin="i3lock-color"
+    suspend_command="systemctl suspend"
+    convert_command="magick"
+    composite_command="magick composite"
+
+    if ! cmd_exists "$i3lockcolor_bin" && cmd_exists "i3lock"; then
+        i3lockcolor_bin="i3lock"
+    fi
+
+    if ! cmd_exists "magick"; then
+        convert_command="convert"
+        composite_command="composite"
+    fi
+
+    # default theme
+    loginbox=00000066
+    loginshadow=00000000
+    locktext="Type password to unlock..."
+    font="sans-serif"
+    ringcolor=ffffffff
+    insidecolor=00000000
+    separatorcolor=00000000
+    ringvercolor=ffffffff
+    insidevercolor=00000000
+    ringwrongcolor=ffffffff
+    insidewrongcolor=d23c3dff
+    timecolor=ffffffff
+    time_format="%H:%M:%S"
+    greetercolor=ffffffff
+    layoutcolor=ffffffff
+    keyhlcolor=d23c3dff
+    bshlcolor=d23c3dff
+    veriftext="Verifying..."
+    verifcolor=ffffffff
+    wrongtext="Failure!"
+    wrongcolor=d23c3dff
+    modifcolor=d23c3dff
+    bgcolor=000000ff
+    wallpaper_cmd="feh --bg-fill"
+
+    # read user config
+    USER_CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}"
+    USER_CONF="$USER_CONF_DIR/betterlockscreenrc"
+    SYS_CONF="/etc/betterlockscreenrc"
+    XDG_USER_CONF_DIR="$USER_CONF_DIR/betterlockscreen"
+    XDG_USER_CONF="$XDG_USER_CONF_DIR/betterlockscreenrc"
+
+    if [ -e "$SYS_CONF" ]; then
+        # shellcheck source=/dev/null
+        source "$SYS_CONF"
+    fi
+
+    if [ -e "$USER_CONF" ]; then
+        echof error "Please, migrate your config $USER_CONF to $XDG_USER_CONF. Old location will soon be deprecated."
+        echof info "mkdir -p ~/.config/betterlockscreen/ && mv $USER_CONF $XDG_USER_CONF"
+
+        # shellcheck source=/dev/null
+        source "$USER_CONF"
+    fi
+
+    if [ -e "$XDG_USER_CONF" ]; then
+        # shellcheck source=/dev/null
+        source "$XDG_USER_CONF"
+    fi
+
+    if ! cmd_exists "$i3lockcolor_bin"; then
+        echof error "Unable to find i3lock-color binary under detected/configured name: '$i3lockcolor_bin'!"
+        exit
+    fi
+
+    # Please make sure to adjust this before release!
+    VERSION="4.3.0"
+
+    # paths
+    CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/betterlockscreen"
+    CUR_DIR="$CACHE_DIR/current"
+
+    # wallpaper
+    CUR_W_RESIZE="$CUR_DIR/wall_resize.png"
+    CUR_W_DIM="$CUR_DIR/wall_dim.png"
+    CUR_W_BLUR="$CUR_DIR/wall_blur.png"
+    CUR_W_DIMBLUR="$CUR_DIR/wall_dimblur.png"
+    CUR_W_PIXEL="$CUR_DIR/wall_pixel.png"
+    CUR_W_DIMPIXEL="$CUR_DIR/wall_dimpixel.png"
+    CUR_W_COLOR="$CUR_DIR/wall_color.png"
+
+    # lockscreen
+    CUR_L_RESIZE="$CUR_DIR/lock_resize.png"
+    CUR_L_DIM="$CUR_DIR/lock_dim.png"
+    CUR_L_BLUR="$CUR_DIR/lock_blur.png"
+    CUR_L_DIMBLUR="$CUR_DIR/lock_dimblur.png"
+    CUR_L_PIXEL="$CUR_DIR/lock_pixel.png"
+    CUR_L_DIMPIXEL="$CUR_DIR/lock_dimpixel.png"
+    CUR_L_COLOR="$CUR_DIR/lock_color.png"
+
+    # Original DPMS timeout
+    DEFAULT_TIMEOUT=$(cut -d ' ' -f4 <<< "$(xset q | sed -n '25p')")
+    # Original DPMS status
+    DEFAULT_DPMS=$(xset q | awk '/^[[:blank:]]*DPMS is/ {print $(NF)}')
+
+    # Dunst
+    DUNST_INSTALLED=false && [[ -e "$(command -v dunstctl)" ]] && DUNST_INSTALLED=true
+    DUNST_IS_RUNNING=false && [[ "$DUNST_INSTALLED" == "true" ]] && [[ "$(pgrep -c dunst)" -gt 0 ]] && DUNST_IS_RUNNING=true
+    DUNST_IS_PAUSED=false && [[ "$DUNST_IS_RUNNING" == "true" ]] && DUNST_IS_PAUSED=$(dunstctl is-paused)
+
+    # Feh
+    FEH_INSTALLED=false && [[ -e "$(command -v feh)" ]] && FEH_INSTALLED=true
+}
+
+# called before screen is locked
+prelock() {
+    # set dpms timeout
+    if [ "$DEFAULT_DPMS" == "Enabled" ]; then
+        xset dpms "$lock_timeout"
+    fi
+
+    # If dusnt is already paused don't pause it again
+    if [[ "$DUNST_IS_RUNNING" == "true" && "$DUNST_IS_PAUSED" == "false" ]]; then
+        dunstctl set-paused true
+    fi
+
+    if [ -e "$XDG_USER_CONF_DIR/custom-pre.sh" ]; then
+        # shellcheck source=/dev/null
+        source "$XDG_USER_CONF_DIR/custom-pre.sh"
+    fi
+}
+
+# lock screen with specified image
+lock() {
+    local image="$1"
+    local fontlg=32
+    local fontmd=16
+    local fontsm=12
+
+    if [ -f "$image" ]; then
+        echof act "Locking screen..."
+    else
+        echof act "Locking screen... (FAILSAFE MODE)"
+    fi
+
+    $i3lockcolor_bin \
+        ${image:+-i "$image"} \
+        --color "$bgcolor" \
+        ${display_on:+-S "$display_on"} \
+        --ind-pos="x+310:y+h-80" \
+        --radius=25 \
+        --ring-width=5 \
+        --inside-color="$insidecolor" \
+        --ring-color="$ringcolor" \
+        --separator-color=$separatorcolor \
+        --insidever-color="$insidevercolor" \
+        --insidewrong-color="$insidewrongcolor" \
+        --ringver-color="$ringvercolor" \
+        --ringwrong-color="$ringwrongcolor" \
+        --line-uses-inside \
+        --keyhl-color="$keyhlcolor" \
+        --bshl-color="$bshlcolor" \
+        --clock --force-clock \
+        --time-pos="ix-265:iy-10" \
+        --time-align 1 \
+        --time-str "$time_format" \
+        --time-color="$timecolor" \
+        --time-font="$font" \
+        --time-size="$fontlg" \
+        --date-str "" \
+        --greeter-pos="ix-265:iy+12" \
+        --greeter-align 1 \
+        --greeter-text "$locktext" \
+        --greeter-color="$greetercolor" \
+        --greeter-font="$font" \
+        --greeter-size="$fontmd" \
+        --layout-pos="ix-265:iy+32" \
+        --layout-align 1 \
+        --layout-color="$layoutcolor" \
+        --layout-font="$font" \
+        --layout-size="$fontsm" \
+        --verif-pos="ix+35:iy-34" \
+        --verif-align 2 \
+        --verif-text="$veriftext" \
+        --verif-color="$verifcolor" \
+        --verif-font="$font" \
+        --verif-size="$fontsm" \
+        --wrong-pos="ix+24:iy-34" \
+        --wrong-align 2 \
+        --wrong-text="$wrongtext" \
+        --wrong-color="$wrongcolor" \
+        --wrong-font="$font" \
+        --wrong-size="$fontsm" \
+        --modif-pos="ix+45:iy+43" \
+        --modif-align 2 \
+        --modif-size="$fontsm" \
+        --modif-color="$modifcolor" \
+        --noinput-text="" \
+        --pass-media-keys \
+        --pass-screen-keys \
+        --pass-volume-keys \
+        --pass-power-keys \
+        "${lockargs[@]}"
+}
+
+# called after screen is unlocked
+postlock() {
+    # restore default dpms timeout
+    if [ "$DEFAULT_DPMS" == "Enabled" ]; then
+        xset dpms "$DEFAULT_TIMEOUT"
+    fi
+
+    # If dunst already paused before locking don't unpause dunst
+    if [[ "$DUNST_IS_RUNNING" == "true" && "$DUNST_IS_PAUSED" == "false" ]]; then
+        dunstctl set-paused false
+    fi
+
+    if [ -e "$XDG_USER_CONF_DIR/custom-post.sh" ]; then
+        # shellcheck source=/dev/null
+        source "$XDG_USER_CONF_DIR/custom-post.sh"
+    fi
+}
+
+# select effect and lock screen
+lockinit() {
+    if pgrep -u "$USER" "$i3lockcolor_bin"; then
+        echof error "i3lock already running"
+        exit 1
+    fi
+
+    echof act "Running prelock..."
+    prelock
+
+    if [[ $runsuspend ]]; then
+        lockselect "$@" &
+        $suspend_command
+        wait $!
+    else
+        lockselect "$@"
+    fi
+
+    echof act "Running postlock..."
+    postlock
+}
+
+lockselect() {
+    case "$1" in
+        dim) lock "$CUR_L_DIM" ;;
+        blur) lock "$CUR_L_BLUR" ;;
+        dimblur) lock "$CUR_L_DIMBLUR" ;;
+        pixel) lock "$CUR_L_PIXEL" ;;
+        dimpixel) lock "$CUR_L_DIMPIXEL" ;;
+        color) lock "$CUR_L_COLOR" ;;
+        *) lock "$CUR_L_RESIZE" ;;
+    esac
+}
+
+# calculate adjustments for hidpi displays
+logical_px() {
+    # $1: number of pixels to convert
+    # $2: 1 for width. 2 for height
+    local pixels="$1"
+    local direction="$2"
+    local dpi
+
+    # use DPI set by user in .Xresources
+    dpi=$(xrdb -q | grep -oP '^\s*Xft.dpi:\s*\K\d+' | bc)
+
+    # or get dpi value from xdpyinfo
+    if [ -z "$dpi" ]; then
+        dpi=$(xdpyinfo | sed -En "s/\s*resolution:\s*([0-9]*)x([0-9]*)\s.*/\\$direction/p" | head -n1)
+    fi
+
+    # adjust scaling
+    if [ -n "$dpi" ]; then
+        local scale
+        scale=$(echo "scale=3; $dpi / 96.0" | bc)
+        echo "$scale * $pixels / 1" | bc
+    else
+        # return the default value if no DPI is set
+        echo "$pixels"
+    fi
+}
+
+# get total resolution, sets $TOTAL_SIZE
+get_total_size () {
+    TOTAL_SIZE=$(xdpyinfo | grep -w "dimensions" | sed -r 's/^[^0-9]*([0-9]+x[0-9]+).*$/\1/')
+}
+
+# get list of displays, sets $DISPLAY_LIST
+get_display_list () {
+    local count=0
+    mapfile -t displays < <(xrandr --listactivemonitors)
+    for display in "${displays[@]:1}"; do
+        ((count++))
+        display="$(echo "$display" | sed -r 's/\/[0-9]*//g')"
+        IFS=' ' read -r -a info  <<< "$display"
+        DISPLAY_LIST+=("$count ${info[3]} ${info[2]}")
+    done
+}
+
+# populate $WALL_LIST depending on number of displays and images passed
+get_wall_list() {
+    local paths=("$@")
+    declare -ga WALL_LIST
+
+    # multiple images and spanning conflict, bail out
+    if [ "${#paths[@]}" -gt 1 ] && [ "$span_image" = true ]; then
+        echof err "Can't use --span with multiple images!"
+        exit 1
+    fi
+
+    # if spanning return 1 image
+    if [ "$span_image" = true ]; then
+        get_image "${paths[0]}"
+
+    # if # paths is 1
+    elif [ "${#paths[@]}" -eq 1 ]; then
+        for ((i=0; i<${#DISPLAY_LIST[@]}; i++)); do
+            # add same image to $WALL_LIST for each display
+            get_image "${paths[0]}"
+        done
+
+    # if # of paths equals # of displays
+    elif [ ${#paths[@]} -eq "${#DISPLAY_LIST[@]}" ]; then
+        for ((i=0; i<${#DISPLAY_LIST[@]}; i++)); do
+            # add each image to $WALL_LIST
+            get_image "${paths[$i]}"
+        done
+
+    # if # of paths differ from # of display, bail out
+    else
+        echof err "${#paths[@]} images provided for ${#DISPLAY_LIST[@]} displays!"
+        exit 1
+    fi
+}
+
+# get image path, append to $WALL_LIST
+get_image() {
+    local path="$1"
+
+    # we have a file
+    if [ -f "$path" ]; then
+        WALL_LIST+=("$path")
+        return
+    # we have a directory
+    elif [ -d "$path" ]; then
+        dir=("$path"/*)
+        rdir="${dir[RANDOM % ${#dir[@]}]}"
+        get_image "$rdir" # <-- calls itself
+    # not file or directory, bail out
+    else
+        echof err "invalid path: $path"
+        exit 1
+    fi
+
+}
+
+# scale base image and generate effects
+resize_and_render () {
+    local base="$1"
+    local path="$2"
+    local resolution="$3"
+
+    # resource paths
+    RES_RESIZE="$path/resize.png"
+    RES_DIM="$path/dim.png"
+    RES_BLUR="$path/blur.png"
+    RES_DIMBLUR="$path/dimblur.png"
+    RES_PIXEL="$path/pixel.png"
+    RES_DIMPIXEL="$path/dimpixel.png"
+    RES_COLOR="$path/color.png"
+
+    # resize
+    base_resize "$base" "$RES_RESIZE" "$resolution"
+
+    # effects
+    for effect in "${fx_list[@]}"; do
+        case $effect in
+            dim) fx_dim "$RES_RESIZE" "$RES_DIM";;
+            blur) fx_blur "$RES_RESIZE" "$RES_BLUR" "$resolution";;
+            dimblur) fx_dimblur "$RES_RESIZE" "$RES_DIMBLUR" "$resolution";;
+            pixel) fx_pixel "$RES_RESIZE" "$RES_PIXEL";;
+            dimpixel) fx_dimpixel "$RES_RESIZE" "$RES_DIMPIXEL";;
+            color) fx_color "$RES_COLOR" "$resolution";;
+        esac
+    done
+
+}
+
+# apply resize
+base_resize() {
+    local input="$1"
+    local output="$2"
+    local size="$3"
+
+    echof act "Resizing base image..."
+    eval $convert_command "$input" \
+        -resize "$size""^" \
+        -gravity center \
+        -extent "$size" \
+        "$output"
+}
+
+# apply dim
+fx_dim() {
+    local input="$1"
+    local output="$2"
+
+    echof act "Rendering 'dim' effect..."
+    eval $convert_command "$input" \
+        -fill black -colorize "$dim_level"% \
+        "$output"
+}
+
+# apply blur
+fx_blur() {
+    local input="$1"
+    local output="$2"
+    local size="$3"
+
+    echof act "Rendering 'blur' effect..."
+    blur_shrink=$(echo "scale=2; 20 / $blur_level" | bc)
+    blur_sigma=$(echo "scale=2; 0.6 * $blur_level" | bc)
+    eval $convert_command "$input" \
+        -filter Gaussian \
+        -resize "$blur_shrink%" \
+        -define "filter:sigma=$blur_sigma" \
+        -resize "$size^" -gravity center -extent "$size" \
+        "$output"
+}
+
+# apply dimblur
+fx_dimblur() {
+    local input="$1"
+    local output="$2"
+    local size="$3"
+
+    echof act "Rendering 'dimblur' effect..."
+    blur_shrink=$(echo "scale=2; 20 / $blur_level" | bc)
+    blur_sigma=$(echo "scale=2; 0.6 * $blur_level" | bc)
+    eval $convert_command "$input" \
+        -fill black -colorize "$dim_level"% \
+        -filter Gaussian \
+        -resize "$blur_shrink%" \
+        -define "filter:sigma=$blur_sigma" \
+        -resize "$size^" -gravity center -extent "$size" \
+        "$output"
+}
+
+# pixelate
+fx_pixel() {
+    local input="$1"
+    local output="$2"
+
+    echof act "Rendering 'pixel' effect..."
+    IFS=',' read -ra range <<< "$pixel_scale"
+    eval $convert_command "$input" \
+        -scale "${range[0]}"% -scale "${range[1]}"% \
+        "$output"
+}
+
+# apply dimpixel
+fx_dimpixel() {
+    local input="$1"
+    local output="$2"
+
+    echof act "Rendering 'dimpixel' effect..."
+    IFS=',' read -ra range <<< "$pixel_scale"
+    eval $convert_command "$input" \
+        -fill black -colorize "$dim_level"% \
+        -scale "${range[0]}"% -scale "${range[1]}"% \
+        "$output"
+}
+
+# create solid color
+fx_color() {
+    local output="$1"
+    local size="$2"
+
+    echof act "Rendering 'color' effect..."
+    eval $convert_command -size "$size" canvas:\#"$solid_color" "$RES_COLOR"
+}
+
+# create loginbox rectangle, set "$RECTANGLE"
+create_loginbox () {
+    RECTANGLE="$CUR_DIR/rectangle.png"
+    local shadow="$CUR_DIR/shadow.png"
+    local width height
+    width=$(logical_px 340 1)
+    height=$(logical_px 100 2)
+    $convert_command -size "$width"x"$height" xc:\#"$loginbox" -fill none "$RECTANGLE"
+    $convert_command "$RECTANGLE" \
+        \( -clone 0 -background \#"$loginshadow" -shadow 100x5+0+0 \) +swap \
+        -background none -layers merge +repage "$shadow"
+    $composite_command -compose Dst_Out -gravity center \
+        "$RECTANGLE" "$shadow" -alpha Set "$shadow"
+    $convert_command "$shadow" "$RECTANGLE" -geometry +10+10 -composite "$RECTANGLE"
+    [[ "$shadow" ]] && rm "$shadow"
+}
+
+# create rectangle with description, set "$DESCRECT"
+create_description () {
+    DESCRECT="$CUR_DIR/description.png"
+    local shadow="$CUR_DIR/shadow.png"
+    $convert_command -background none -family "$(fc-match "$font" family)" -style Normal -pointsize 14 -fill \#"$greetercolor" label:"\ $description\ " -bordercolor \#"$loginbox" -border 10 "$DESCRECT"
+    $convert_command "$DESCRECT" \
+        \( -clone 0 -background \#"$loginshadow" -shadow 100x5+0+0 \) +swap \
+        -background none -layers merge +repage "$shadow"
+    $composite_command -compose Dst_Out -gravity center \
+        "$DESCRECT" "$shadow" -alpha Set "$shadow"
+    $convert_command "$shadow" "$DESCRECT" -geometry +10+10 -composite "$DESCRECT"
+    [[ "$shadow" ]] && rm "$shadow"
+}
+
+# delete and recreate directory
+purge_cache () {
+    if [[ -d "$1" ]]; then
+        rm -r "$1"
+    fi
+    mkdir -p "$1"
+}
+
+# update lockscreen and wallpaper images
+update () {
+    local images=("$@")
+
+    echof act "Updating image cache..."
+    mkdir -p "$CACHE_DIR" &>/dev/null
+
+    get_display_list # DISPLAY_LIST
+    get_total_size # TOTAL_SIZE
+    echof info "Detected ${#DISPLAY_LIST[@]} display(s) @ $TOTAL_SIZE total resolution"
+
+    get_wall_list "${images[@]}" # WALL_LIST
+    echof info "Original image(s): ${WALL_LIST[*]##*/}"
+
+    # Prepare description box to obtain width for positioning
+    local descwidth
+    local descheight
+    if [ -z "$description" ]; then
+        descwidth=0
+        descheight=0
+    else
+        create_description
+        descwidth=$(identify -format "%[fx:w]" "$DESCRECT")
+        descheight=$(identify -format "%[fx:h]" "$DESCRECT")
+    fi
+
+    for ((i=0; i<${#DISPLAY_LIST[@]}; i++)); do
+        display="${DISPLAY_LIST[$i]}"
+        USER_WALL="${WALL_LIST[$i]}"
+
+        # escape spaces for IM
+        if echo "$USER_WALL" | grep -E -q "[[:space:]]"; then
+            USER_WALL="${USER_WALL// /\\ }"
+        fi
+
+        IFS=' ' read -r -a dinfo  <<< "$display"
+        local id="${dinfo[0]}"
+        local device="${dinfo[1]}"
+        local geometry="${dinfo[2]}"
+
+        read -r -a cols <<< "${geometry//[x+-]/ }"
+        local position="${geometry#*"${cols[1]}"}"
+        local resolution="${geometry%"${position}"*}"
+
+        if [[ $id -eq "$display_on" ]] || [[ "$display_on" -eq 0 ]]; then
+
+            IFS='x' read -r -a dimension <<< "$resolution"
+            res_x="${dimension[0]}"
+            res_y="${dimension[1]}"
+            read -r -a val <<< "${position//[+-]/ }"
+            read -r -a sym <<< "${position//[0-9]/ }"
+            pos_x="${sym[0]}${val[0]}"
+            pos_y="${sym[1]}${val[1]}"
+
+            rect_x=$((pos_x + $(logical_px 15 1)))
+            rect_y=$((pos_y + res_y - $(logical_px 140 2)))
+            positions+=("+$((rect_x))+$((rect_y))")
+
+            descrect_x=$((pos_x + res_x - descwidth - $(logical_px 15 1)))
+            descrect_y=$((pos_y + res_y - descheight - $(logical_px 20 2)))
+            positions_desc+=("+$((descrect_x))+$((descrect_y))")
+        fi
+
+        local path="$CACHE_DIR/$id-$device"
+        purge_cache "$path"
+
+        if [ "$span_image" = true ]; then
+            if [ "$id" -gt 1 ]; then
+                continue
+            else
+                device="[span]"
+                id="*"
+                resolution="$TOTAL_SIZE"
+            fi
+        fi
+
+        echof info "Processing display: $device ($id)"
+        echof info "Resolution: $resolution"
+
+        if [ "$span_image" = true ]; then
+            resize_and_render "$USER_WALL" "$path" "$resolution"
+        else
+            resize_and_render "$USER_WALL" "$path" "$resolution"
+
+            PARAM_RESIZE="$PARAM_RESIZE $RES_RESIZE -geometry $position -composite "
+            PARAM_DIM="$PARAM_DIM $RES_DIM -geometry $position -composite "
+            PARAM_BLUR="$PARAM_BLUR $RES_BLUR -geometry $position -composite "
+            PARAM_DIMBLUR="$PARAM_DIMBLUR $RES_DIMBLUR -geometry $position -composite "
+            PARAM_PIXEL="$PARAM_PIXEL $RES_PIXEL -geometry $position -composite "
+            PARAM_DIMPIXEL="$PARAM_DIMPIXEL $RES_DIMPIXEL -geometry $position -composite "
+            PARAM_COLOR="$PARAM_COLOR $RES_COLOR -geometry $position -composite "
+        fi
+
+    done
+
+    purge_cache "$CUR_DIR"
+
+    if [ "$span_image" = true ] || [ ${#DISPLAY_LIST[@]} -lt 2 ]; then
+        echof act "Rendering final wallpaper images..."
+        [[ -f "$RES_RESIZE" ]] && eval "cp $RES_RESIZE $CUR_W_RESIZE"
+        [[ -f "$RES_DIM" ]] && eval "cp $RES_DIM $CUR_W_DIM"
+        [[ -f "$RES_BLUR" ]] && eval "cp $RES_BLUR $CUR_W_BLUR"
+        [[ -f "$RES_DIMBLUR" ]] && eval "cp $RES_DIMBLUR $CUR_W_DIMBLUR"
+        [[ -f "$RES_PIXEL" ]] && eval "cp $RES_PIXEL $CUR_W_PIXEL"
+        [[ -f "$RES_DIMPIXEL" ]] && eval "cp $RES_DIMPIXEL $CUR_W_DIMPIXEL"
+        [[ -f "$RES_COLOR" ]] && eval "cp $RES_COLOR $CUR_W_COLOR"
+    else
+        echof act "Creating canvas: $TOTAL_SIZE"
+        [[ -f "$RES_RESIZE" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_RESIZE"
+        [[ -f "$RES_DIM" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_DIM"
+        [[ -f "$RES_BLUR" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_BLUR"
+        [[ -f "$RES_DIMBLUR" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_DIMBLUR"
+        [[ -f "$RES_PIXEL" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_PIXEL"
+        [[ -f "$RES_DIMPIXEL" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_DIMPIXEL"
+        [[ -f "$RES_COLOR" ]] && eval "$convert_command -size $TOTAL_SIZE 'xc:blue' $CUR_W_COLOR"
+
+        echof act "Rendering final wallpaper images..."
+        [[ -f "$CUR_W_RESIZE" ]] && eval "$convert_command $CUR_W_RESIZE $PARAM_RESIZE $CUR_W_RESIZE"
+        [[ -f "$CUR_W_DIM" ]] && eval "$convert_command $CUR_W_DIM $PARAM_DIM $CUR_W_DIM"
+        [[ -f "$CUR_W_BLUR" ]] && eval "$convert_command $CUR_W_BLUR $PARAM_BLUR $CUR_W_BLUR"
+        [[ -f "$CUR_W_DIMBLUR" ]] && eval "$convert_command $CUR_W_DIMBLUR $PARAM_DIMBLUR $CUR_W_DIMBLUR"
+        [[ -f "$CUR_W_PIXEL" ]] && eval "$convert_command $CUR_W_PIXEL $PARAM_PIXEL $CUR_W_PIXEL"
+        [[ -f "$CUR_W_DIMPIXEL" ]] && eval "$convert_command $CUR_W_DIMPIXEL $PARAM_DIMPIXEL $CUR_W_DIMPIXEL"
+        [[ -f "$CUR_W_COLOR" ]] && eval "$convert_command $CUR_W_COLOR $PARAM_COLOR $CUR_W_COLOR"
+    fi
+
+    echof act "Rendering final lockscreen images..."
+
+    create_loginbox
+    for pos in "${positions[@]}"; do
+        PARAM_RECT="$PARAM_RECT $RECTANGLE -geometry $pos -composite "
+    done
+
+    if [ -n "$description" ]; then
+        create_description
+        for descpos in "${positions_desc[@]}"; do
+            PARAM_RECT="$PARAM_RECT $DESCRECT -geometry $descpos -composite "
+        done
+    fi
+
+    [[ -f "$CUR_W_RESIZE" ]] && eval "$convert_command $CUR_W_RESIZE $PARAM_RECT $CUR_L_RESIZE"
+    [[ -f "$CUR_W_DIM" ]] && eval "$convert_command $CUR_W_DIM $PARAM_RECT $CUR_L_DIM"
+    [[ -f "$CUR_W_BLUR" ]] && eval "$convert_command $CUR_W_BLUR $PARAM_RECT $CUR_L_BLUR"
+    [[ -f "$CUR_W_DIMBLUR" ]] && eval "$convert_command $CUR_W_DIMBLUR $PARAM_RECT $CUR_L_DIMBLUR"
+    [[ -f "$CUR_W_PIXEL" ]] && eval "$convert_command $CUR_W_PIXEL $PARAM_RECT $CUR_L_PIXEL"
+    [[ -f "$CUR_W_DIMPIXEL" ]] && eval "$convert_command $CUR_W_DIMPIXEL $PARAM_RECT $CUR_L_DIMPIXEL"
+    [[ -f "$CUR_W_COLOR" ]] && eval "$convert_command $CUR_W_COLOR $PARAM_RECT $CUR_L_COLOR"
+
+    [[ "$RECTANGLE" ]] && rm "$RECTANGLE"
+    [[ "$DESCRECT" ]] && rm "$DESCRECT"
+
+    echof ok "Done"
+
+}
+
+# set wallpaper with effect
+wallpaper() {
+    local effect="$1"
+
+    # make wallpaper span displays
+    get_display_list
+    if [ "$span_image" = true ] || [[ "${#DISPLAY_LIST[@]}" -gt 1 ]]; then
+        wallpaper_cmd="$wallpaper_cmd --no-xinerama"
+    fi
+
+    # set wallpaper
+    case "$effect" in
+        dim) wallpaper="$CUR_W_DIM";;
+        blur) wallpaper="$CUR_W_BLUR";;
+        dimblur) wallpaper="$CUR_W_DIMBLUR";;
+        pixel) wallpaper="$CUR_W_PIXEL";;
+        dimpixel) wallpaper="$CUR_W_DIMPIXEL";;
+        color) wallpaper="$CUR_W_COLOR";;
+        *) wallpaper="$CUR_W_RESIZE";;
+    esac
+    eval "$wallpaper_cmd $wallpaper"
+}
+
+# wrap echo with fancy prefix
+echof() {
+    local prefix="$1"
+    local message="$2"
+
+    case "$prefix" in
+        header) msgpfx="[\e[1;95mB\e[m]";;
+        info) msgpfx="[\e[1;97m=\e[m]";;
+        act) msgpfx="[\e[1;92m*\e[m]";;
+        ok) msgpfx="[\e[1;93m+\e[m]";;
+        error) msgpfx="[\e[1;91m!\e[m]";;
+        *) msgpfx="";;
+    esac
+    [ "$quiet" != true ] && echo -e "$msgpfx $message"
+}
+
+# help message
+usage() {
+    echo
+    echo "Usage: betterlockscreen [-u <PATH>] [-l <EFFECT>] [-w <EFFECT>]"
+    echo
+    echo "  -q --quiet"
+    echo "      Do not produce any text output on locking"
+    echo
+    echo "  -u --update <PATH>"
+    echo "      Update lock screen image"
+    echo
+    echo "  -l --lock <EFFECT>"
+    echo "      Lock screen with cached image"
+    echo
+    echo "  -w --wall <EFFECT>"
+    echo "      Set wallpaper with cached image"
+    echo
+    echo "Additional arguments:"
+    echo
+    echo "  --display <N>"
+    echo "      Set display to draw loginbox"
+    echo
+    echo "  --span"
+    echo "      Scale image to span multiple displays"
+    echo
+    echo "  --off <N>"
+    echo "      Turn display off after N seconds"
+    echo
+    echo "  --fx <EFFECT,EFFECT,EFFECT>"
+    echo "      List of effects to generate"
+    echo
+    echo "  --desc <DESCRIPTION>"
+    echo "      Set a description for the new lock screen image"
+    echo "      (Only has an effect in combination with --update)"
+    echo
+    echo "  --show-layout"
+    echo "      Show current keyboard layout"
+    echo
+    echo "  --wallpaper-cmd <command>"
+    echo "      to set your custom wallpaper setter"
+    echo
+    echo "  --time-format <format>"
+    echo "      to set the time format used by i3lock-color"
+    echo
+    echo "  -- <ARGS>"
+    echo "      Pass additional arguments to i3lock"
+    echo
+    echo "Effects arguments:"
+    echo
+    echo "  --dim <N>"
+    echo "      Dim image N percent (0-100)"
+    echo
+    echo "  --blur <N>"
+    echo "      Blur image N amount (0.0-1.0)"
+    echo
+    echo "  --pixel <N,N>"
+    echo "      Pixelate image with N shrink and N grow (unsupported)"
+    echo
+    echo "  --color <HEX>"
+    echo "      Solid color background with HEX"
+    echo
+    exit 1
+}
+
+lockargs=(-n)
+
+init_config
+
+# show usage when no arguments passed
+[[ "$1" = "" ]] && usage
+
+# process arguments
+for arg in "$@"; do
+    [[ "${arg:0:1}" = '-' ]] || continue
+
+    case "$1" in
+
+        -q | --quiet)
+            quiet=true
+            shift
+            ;;
+
+        -u | --update)
+            runupdate=true
+            imagepaths+=("$2")
+            shift 2
+            ;;
+
+        -s | --suspend)
+            runsuspend=true
+            ;&
+
+        -l | --lock)
+            runlock=true
+            if [[ ${2:0:1} = '-' ]]; then
+                shift 1
+            else
+                lockstyle="$2"; shift 2
+            fi
+            ;;
+
+        -w | --wall)
+            wallpaper "$2"
+            shift 2
+            ;;
+
+        --wallpaper-cmd)
+            wallpaper_cmd="$2"
+            shift 2
+            ;;
+
+        --time-format)
+            time_format="$2"
+            shift 2
+            ;;
+
+        --display)
+            display_on="$2"
+            shift 2
+            ;;
+
+        --span)
+            span_image=true
+            shift 1
+            ;;
+
+        --off)
+            lock_timeout="$2"
+            shift 2
+            ;;
+
+        --text)
+            locktext="$2"
+            shift 2
+            ;;
+
+        --show-layout)
+            keylayout="$2";
+            lockargs+=(--keylayout "${keylayout:-0}")
+            shift 2
+            ;;
+
+        --fx)
+            IFS=',' read -ra fx_list <<< "$2"
+            shift 2
+            ;;
+
+        --dim)
+            dim_level="$2"
+            shift 2
+            ;;
+
+        --blur)
+            blur_level="$2"
+            shift 2
+            ;;
+
+        --pixel)
+            pixel_scale="$2"
+            shift 2
+            ;;
+
+        --color)
+            solid_color="${2//\#/}"
+            shift 2
+            ;;
+
+        --desc)
+            description="$2"
+            shift 2
+            ;;
+
+        -v | --version)
+            echo
+            echo "Betterlockscreen: version: v$VERSION (dunst: $DUNST_INSTALLED, feh: $FEH_INSTALLED)"
+            $i3lockcolor_bin --version
+            $convert_command --version
+
+            if [[ "$DUNST_INSTALLED" == "true" ]]; then
+                dunstctl debug
+            fi
+
+            if [[ "$FEH_INSTALLED" == "true" ]]; then
+                feh --version
+            fi
+
+            break
+            ;;
+
+        --)
+            lockargs+=("${@:2}")
+            break
+            ;;
+
+        -h | --help | *)
+            usage
+            ;;
+    esac
+done
+
+echof header "Betterlockscreen"
+
+# Run image generation
+[[ $runupdate ]] && update "${imagepaths[@]}"
+
+# Activate lockscreen
+[[ $runlock ]] && lockinit "$lockstyle"
+
+exit 0
diff --git a/home-bin/blackpearl-appsmenu.sh b/home-bin/blackpearl-appsmenu.sh
new file mode 100644 (file)
index 0000000..6c499c4
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# rofi -no-lazy-grab -show drun -theme blackpearl/appmenu
+rofi -no-lazy-grab -show drun -theme darknix/appmenu.rasi
+
diff --git a/home-bin/blackpearl-emoji.sh b/home-bin/blackpearl-emoji.sh
new file mode 100644 (file)
index 0000000..a240225
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rofi -no-lazy-grab -modi emoji -show emoji -theme darknix/runner.rasi
+
diff --git a/home-bin/blackpearl-notes.sh b/home-bin/blackpearl-notes.sh
new file mode 100644 (file)
index 0000000..a11244a
--- /dev/null
@@ -0,0 +1,28 @@
+#! /bin/bash
+
+rofi_command="rofi -theme darknix/notes.rasi"
+
+# list of existing notes passed to rofi
+options=$(notes -pl)
+
+# how to run the script
+PARAM="$1"
+case $PARAM in
+       -a )
+               alacritty --hold --class notes --title notes -e notes -a
+               ;;
+       -e )
+               # note_ID
+               selected=$(echo -e "$options" | $rofi_command -p "edit note" -dmenu -selected-row 0 | cut -d " " -f1)
+               alacritty --hold --class notes --title notes -e notes -e ${selected}
+               ;;
+       -d )
+               # note_ID
+               selected=$(echo -e "$options" | $rofi_command -p "delete note" -dmenu -selected-row 0 | cut -d " " -f1)
+               alacritty --hold --class notes --title notes -e notes -d ${selected}
+               ;;
+       * )
+               selected=$(echo -e "$options" | $rofi_command -p "notes" -dmenu -selected-row 0 | cut -d " " -f1)
+               alacritty --hold --class notes --title notes -e notes -s $selected
+               ;;
+esac
diff --git a/home-bin/blackpearl-powermenu.sh b/home-bin/blackpearl-powermenu.sh
new file mode 100644 (file)
index 0000000..fad6b19
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# rofi_command="rofi -theme blackpearl/powermenu.rasi"
+rofi_command="rofi -theme darknix/powermenu.rasi"
+
+### Options ###
+power_off=""
+reboot=""
+lock=""
+#suspend="⏾" 
+log_out="󰿅"
+# Variable passed to rofi
+options="$power_off\n$reboot\n$lock\n$log_out"
+
+chosen="$(echo -e "$options" | $rofi_command -dmenu -selected-row 0)"
+case $chosen in
+    $power_off)
+        notify-send 'shutting down' 'the system is going to shutdown now'
+        sleep 1
+        /usr/bin/loginctl poweroff
+        ;;
+    $reboot)
+        notify-send 'rebooting the system' 'the system is going to reboot now'
+        sleep 1
+        /usr/bin/loginctl reboot
+        ;;
+    $lock)
+        sleep 1
+        i3lock-fancy
+        ;;
+#    $suspend)
+#        sleep 1
+#        i3suspend
+#        ;;
+    $log_out)
+        i3-exit
+        ;;
+esac
+
diff --git a/home-bin/blackpearl-runner.sh b/home-bin/blackpearl-runner.sh
new file mode 100644 (file)
index 0000000..36517d1
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rofi -no-lazy-grab -show run -theme darknix/runner.rasi
+
diff --git a/home-bin/blackpearl-scrotmenu.sh b/home-bin/blackpearl-scrotmenu.sh
new file mode 100644 (file)
index 0000000..dbafcb7
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+rofi_command="rofi -theme darknix/scrotmenu.rasi"
+
+### Options ###
+screen=""
+area=""
+# Variable passed to rofi
+options="$screen\n$area"
+
+chosen="$(echo -e "$options" | $rofi_command -dmenu -selected-row 1)"
+case $chosen in
+    $screen)
+        i3-scrot -d 3
+        ;;
+    $area)
+        i3-scrot -s
+        ;;
+esac
diff --git a/home-bin/blackpearl-sshmenu.sh b/home-bin/blackpearl-sshmenu.sh
new file mode 100644 (file)
index 0000000..b70d4b9
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rofi -no-lazy-grab -show ssh -no-parse-known-hosts -theme darknix/sshmenu.rasi
+
diff --git a/home-bin/blackpearl-symbols.sh b/home-bin/blackpearl-symbols.sh
new file mode 100644 (file)
index 0000000..5723bd1
--- /dev/null
@@ -0,0 +1 @@
+rofi -no-lazy-grab -show sym -modes "sym:symbols.sh" -theme darknix/runner.rasi
diff --git a/home-bin/blackpearl-utilsmenu.sh b/home-bin/blackpearl-utilsmenu.sh
new file mode 100644 (file)
index 0000000..8c2beaa
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+rofi_command="rofi -theme darknix/utilsmenu.rasi"
+
+### Options ###
+wpaper=""
+webdevel=""
+# Variable passed to rofi
+options="$wpaper\n$webdevel"
+
+chosen="$(echo -e "$options" | $rofi_command -dmenu -selected-row 1)"
+case $chosen in
+    $wpaper)
+        qwalwal.sh
+        ;;
+    $webdevel)
+        webdevel
+        ;;
+esac
+
diff --git a/home-bin/blackpearl-window.sh b/home-bin/blackpearl-window.sh
new file mode 100644 (file)
index 0000000..68d4d4f
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+case $1 in
+       all )
+               rofi -no-lazy-grab -show window -theme darknix/appslist.rasi -window-format " [{w}]     {c}     {t:20}" -window-thumbnail
+               ;;
+       desktop )
+               rofi -no-lazy-grab -show windowcd -theme darknix/appslist.rasi
+               ;;
+esac
+
+
diff --git a/home-bin/change_wallpaper.sh b/home-bin/change_wallpaper.sh
new file mode 100644 (file)
index 0000000..e3d293a
--- /dev/null
@@ -0,0 +1,127 @@
+#! /bin/bash
+
+# uncomment for debug
+# set -x
+
+PID=$$
+PIDFILE=${PIDFILE:-/tmp/wallpaper.pid}
+WAIT_CYCLE="5m"
+INPUT=$1
+
+trap "rm -f $PIDFILE" SIGTERM
+
+WP_SETTER="/usr/bin/feh"
+QARMA="/usr/bin/qarma"
+
+function set_wp() {
+       NEW_WP=$1
+       $WP_SETTER --bg-fill $NEW_WP
+}
+
+# set background function (requires input)
+function wpapers() {
+       if [[ -f $1 ]]; then
+               # We have a single file as input
+               set_wp $1
+               exit 0
+       elif [[ -d $1 ]]; then
+               # directory as input
+               while true; do
+                       BGIMG=$(find $1 -type f -print | shuf -n1)
+                       set_wp ${BGIMG}
+                       sleep $WAIT_CYCLE
+               done
+       fi
+}
+
+function file_or_dir() {
+       if [[ -f /tmp/choice ]]; then
+               rm /tmp/choice
+       fi
+
+       FOD=$($QARMA --list --text="single image or directory?" --hide-header "file" "directory" > /tmp/choice)
+       case $? in
+               0 )
+                       file_chooser $(cat /tmp/choice)
+                       ;;
+               1 )
+                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                       ;;
+               -1 )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function file_chooser() {
+       case $1 in
+               "file" )
+                       FILE="$($QARMA --file-selection --title='Choose your Wallpaper')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               "directory" )
+                       FILE="$($QARMA --file-selection --directory --title='Choose your Wallpaper directory')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               * )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function run() {
+       if [[ $(basename $0) == "wallpaper.sh" ]]; then
+               # we were called as wallpaper.sh, so simple wallpaper setter without qarma interaction
+               wpapers "$1"
+       elif [[ $(basename $0) == "change_wallpaper.sh" ]]; then
+               # we use qarma to set the wallpaper
+               $QARMA --question --title="Change Wallpaper" --text="Do you want to change the wallpaper?"
+               case $? in
+                       0 )
+                               file_or_dir
+                               ;;
+                       1 )
+                               $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                               ;;
+                       -1 )
+                               $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+               esac
+       fi
+}
+
+if [[ -r $PIDFILE ]]; then
+       # PIDFILE exists, so I guess there's already an instance running
+       # let's kill it and run again
+       kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
+       # should already be deleted by trap, but just to be sure
+       rm /tmp/choice || true
+       rm $PIDFILE
+fi
+
+# create PIDFILE
+echo $PID > $PIDFILE
+
+if [[ ! -z $1 ]]; then
+    run $1
+else
+    run
+fi
+
diff --git a/home-bin/dunst-snooze.sh b/home-bin/dunst-snooze.sh
new file mode 100644 (file)
index 0000000..59bf6b0
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+case "$1" in
+    --toggle)
+        dunstctl set-paused toggle
+        ;;
+    *)
+        if [ "$(dunstctl is-paused)" = "true" ]; then
+            echo "  "
+        else
+            echo "  "
+        fi
+        ;;
+esac
diff --git a/home-bin/dunst_vol_brig.sh b/home-bin/dunst_vol_brig.sh
new file mode 100644 (file)
index 0000000..a255d34
--- /dev/null
@@ -0,0 +1,87 @@
+#! /bin/bash
+# See README.md for usage instructions
+bar_color="#ffbf00"
+volume_step=5
+brightness_step=10
+
+# Uses regex to get volume from pactl
+function get_volume {
+    pactl get-sink-volume @DEFAULT_SINK@ | grep -Po '[0-9]{1,3}(?=%)' | head -1
+}
+
+# Uses regex to get mute status from pactl
+function get_mute {
+    pactl get-sink-mute @DEFAULT_SINK@ | grep -Po '(?<=Mute: )(yes|no)'
+}
+
+# Uses regex to get brightness from xbacklight
+function get_brightness {
+    xbacklight | grep -Po '[0-9]{1,3}(?=\.)'
+}
+
+# Returns a mute icon, a volume-low icon, or a volume-high icon, depending on the volume
+function get_volume_icon {
+    volume=$(get_volume)
+    mute=$(get_mute)
+    if [ $volume -eq 0 ] || [ $mute == "yes" ] ; then
+        volume_icon=""
+    elif [ $volume -lt 50 ]; then
+        volume_icon=""
+    else
+        volume_icon=""
+    fi
+}
+
+# Always returns the same icon - I couldn't get the brightness-low icon to work with fontawesome
+function get_brightness_icon {
+    brightness_icon="󰃠"
+}
+
+# Displays a volume notification using dunstify
+function show_volume_notif {
+    volume=$(get_volume)
+    get_volume_icon
+    if [ $volume -eq 0 ] || [ $mute == "yes" ] ; then
+        volstr="SHHH!!"
+    else
+        volstr=${volume}%
+    fi
+    dunstify -i audio-volume-muted -t 1000 -r 2593 -u normal "$volume_icon $volstr" -h int:value:$volume -h string:hlcolor:$bar_color
+}
+
+# Displays a brightness notification using dunstify
+function show_brightness_notif {
+    brightness=$(get_brightness)
+    get_brightness_icon
+    dunstify -t 1000 -r 2593 -u normal "$brightness_icon $brightness%" -h int:value:$brightness -h string:hlcolor:$bar_color
+}
+
+# Main function - Takes user input, "volume_up", "volume_down", "brightness_up", or "brightness_down"
+case $1 in
+    vol_u)
+        # Unmutes and increases volume, then displays the notification
+        pactl set-sink-mute @DEFAULT_SINK@ 0
+        pactl set-sink-volume @DEFAULT_SINK@ +$volume_step%
+        show_volume_notif
+        ;;
+    vol_d)
+        # Raises volume and displays the notification
+        pactl set-sink-volume @DEFAULT_SINK@ -$volume_step%
+        show_volume_notif
+        ;;
+    mute)
+        # Toggles mute and displays the notification
+        pactl set-sink-mute @DEFAULT_SINK@ toggle
+        show_volume_notif
+        ;;
+    brig_u)
+        # Increases brightness and displays the notification
+        xbacklight -inc $brightness_step -time 0 
+        show_brightness_notif
+        ;;
+    brig_d)
+        # Decreases brightness and displays the notification
+        xbacklight -dec $brightness_step -time 0
+        show_brightness_notif
+        ;;
+esac
diff --git a/home-bin/feh-blur b/home-bin/feh-blur
new file mode 100644 (file)
index 0000000..da1f499
--- /dev/null
@@ -0,0 +1,412 @@
+#!/usr/bin/env bash
+# https://github.com/rstacruz/feh-blur
+
+if [[ "$1" != "--worker" ]]; then
+  # Where to write stuff
+  export CACHE_DIR="/tmp/feh-blur.$$"
+
+  # How much to blur (--blur N)
+  export BLUR_STRENGTH="32"
+
+  # Contrast (--uncontrast)
+  export REDUCE_CONTRAST="0"
+
+  # How much to dim (--dim N)
+  export DIM_STRENGTH="32"
+
+  # Dimmer
+  export DIM_COLOR="#000"
+
+  # Where to write the final blurred image (--save-image PATH)
+  export BLUR_IMAGE_SAVE_LOCATION=""
+
+  # Interval between frames in an animation
+  export ANIMATION_INTERVAL=0.005
+
+  # Fade in and out?
+  export ANIMATE_FADE=1
+
+  # Interval to check with wmctrl
+  export POLL_INTERVAL=0.3
+
+  # The verbosity. --verbose sets this to 2, and --quiet sets it to 0.
+  export VERBOSE=1
+fi
+
+# The name of this program
+export BIN="$(basename "$0")"
+
+# Expression for proc grepping
+PROC_EXPR="bash.*$(basename "$0")"
+
+# Run mode. -d sets this to background (daemon) mode.
+# Values: background | foreground | noop
+MODE=foreground
+
+# Guard so that do_cleanup will only be invoked once.
+CLEANING_UP=0
+
+# Image extension to use. 'ppm' is used because it's faster to read/write to, I
+# think.
+EXT="ppm"
+
+# The original source.
+wall_original="$CACHE_DIR/original.$EXT"
+
+# Echo some info. Only if verbose=1
+info () {
+  if [[ "$VERBOSE" -gt 0 ]]; then
+    echo "    $1"
+  fi
+}
+
+head () {
+  if [[ "$VERBOSE" -gt 0 ]]; then
+    echo ""
+    echo -e " \033[31m>>\033[32m $1\033[0m"
+  fi
+}
+
+# Log some debug info
+debug () {
+  if [[ "$VERBOSE" -gt 1 ]]; then
+    echo "  - $1"
+  fi
+}
+
+# Generate a bunch of images
+generate_blurred_images () {
+  mkdir -p "$CACHE_DIR"
+  local source="$1"
+  local original="$CACHE_DIR/original.$EXT"
+
+  gm convert "$source" -resize 1920x "$original"
+
+  head "Found wallpaper"
+  info "$source"
+  info "Generating blurred images..."
+
+  local fx1=""
+  local fx2=""
+  local fx3=""
+
+  if [[ "$BLUR_STRENGTH" != "0" ]]; then 
+    fx1="$fx1 -scale 25% -blur 0x$(( "$BLUR_STRENGTH" / 4 )) -scale 400%"
+    fx2="$fx2 -scale 25% -blur 0x$(( "$BLUR_STRENGTH" / 2 )) -scale 400%"
+    fx3="$fx3 -scale 25% -blur 0x$(( "$BLUR_STRENGTH" / 1 )) -scale 400%"
+  fi
+
+  if [[ "$REDUCE_CONTRAST" = "1" ]]; then
+    fx1="+contrast $fx1"
+    fx2="+contrast +contrast $fx2"
+    fx3="+contrast +contrast +contrast $fx3"
+  fi
+
+  if [[ "$DIM_STRENGTH" != "0" ]]; then
+    fx1="$fx1 -fill $DIM_COLOR -colorize $(( "$DIM_STRENGTH" / 4 ))%"
+    fx2="$fx2 -fill $DIM_COLOR -colorize $(( "$DIM_STRENGTH" / 2 ))%"
+    fx3="$fx3 -fill $DIM_COLOR -colorize $(( "$DIM_STRENGTH" / 1 ))%"
+  fi
+
+  gm convert "$original" \
+    $fx1 \
+    "$CACHE_DIR/blur-0.$EXT"
+  gm convert "$CACHE_DIR/blur-0.$EXT" \
+    $fx2 \
+    "$CACHE_DIR/blur-1.$EXT"
+  gm convert "$CACHE_DIR/blur-0.$EXT" \
+    $fx3 \
+    "$CACHE_DIR/blur-final.$EXT"
+
+  if [[ -n "$BLUR_IMAGE_SAVE_LOCATION" ]]; then
+    gm convert \
+      "$CACHE_DIR/blur-final.$EXT" \
+      "$BLUR_IMAGE_SAVE_LOCATION"
+  fi
+
+  info "Done."
+}
+
+# Get current feh wallpaper
+get_feh_wallpaper() {
+  tail -n1 "$HOME/.fehbg" | sed 's/--no-fehbg //g' | cut -d"'" -f2
+}
+
+# Get wallpaper mode like --bg-tile
+get_feh_wallpaper_mode() {
+  tail -n1 "$HOME/.fehbg" | sed 's/--no-fehbg //g' | cut -d' ' -f2
+}
+
+# get_current_workspace => "2"
+get_current_workspace() {
+  # 2 * DG: N/A VP: 0,0 WA: N/A Name
+  wmctrl -d | grep '\*' | cut -d' ' -f1
+}
+
+# get_open_windows_count() => "2"
+get_open_windows_count() {
+  workspace="$(get_current_workspace)"
+  wmctrl -l | cut -d' ' -f3 | grep -c "$workspace"
+}
+
+is_blank() {
+  count=$(get_open_windows_count)
+  [[ "$count" -eq 0 ]]
+}
+
+set_blurred_wallpaper() {
+  debug "Setting blurred wallpaper"
+  local mode="$1" # --bg-tile
+
+  if [[ "$ANIMATE_FADE" == 1 ]]; then
+    # We're going to redirect output to /dev/null to supress feh warnings
+    feh --no-fehbg "$mode" "$CACHE_DIR/blur-0.$EXT" &> /dev/null
+    sleep $ANIMATION_INTERVAL
+    feh --no-fehbg "$mode" "$CACHE_DIR/blur-1.$EXT" &> /dev/null
+    sleep $ANIMATION_INTERVAL
+  fi
+
+  feh --no-fehbg "$mode" "$CACHE_DIR/blur-final.$EXT" &> /dev/null
+}
+
+set_original_wallpaper() {
+  debug "Setting original wallpaper"
+  local mode="$1" # --bg-tile
+
+  if [[ "$ANIMATE_FADE" == 1 ]]; then
+    feh --no-fehbg "$mode" "$CACHE_DIR/blur-1.$EXT" &> /dev/null
+    sleep $ANIMATION_INTERVAL
+    feh --no-fehbg "$mode" "$CACHE_DIR/blur-0.$EXT" &> /dev/null
+    sleep $ANIMATION_INTERVAL
+  fi
+
+  feh --no-fehbg "$mode" "$wall_original" &> /dev/null
+}
+
+kill_other_instances() {
+  if [[ "$(pgrep -fcl "$PROC_EXPR")" -gt 1 ]]; then
+    head "Stopping other instances of $BIN..."
+
+    local count=1
+    while [[ "$(pgrep -fcl "$PROC_EXPR")" -gt 1 ]]; do
+      count=$(( count + 1 ))
+      old_pid="$(pgrep -fo "$PROC_EXPR")"
+
+      # Kill it; if it refuses after some time, force-stop it
+      if [[ "$count" -gt 10 ]]; then
+        kill -9 "$old_pid"
+      else
+        kill "$old_pid"
+      fi
+      sleep 0.1
+    done
+  fi
+}
+
+run_loop () {
+  prev_blank="-"
+  prev_wallpaper="-"
+  first_run="1"
+
+  while true; do
+    wallpaper="$(get_feh_wallpaper)"
+
+    # Check if wallpaper has changed.
+    if [[ "$prev_wallpaper" != "$wallpaper" ]]; then
+      wallpaper_mode="$(get_feh_wallpaper_mode)"
+
+      # If there's no wallpaper, try again later.
+      if [[ -z "$wallpaper" ]]; then
+        sleep "$POLL_INTERVAL"
+        continue
+      else
+        generate_blurred_images "$wallpaper"
+        prev_wallpaper="$wallpaper"
+        prev_blank=""
+      fi
+    fi
+
+    blank="$(is_blank && echo 1 || echo 0)"
+    if [[ "$prev_blank" != "$blank" ]]; then
+      if [[ "$blank" == 0 ]]; then
+        set_blurred_wallpaper "$wallpaper_mode"
+      elif [[ "$first_run" == "0" ]]; then
+        # Skip set_original_wallpaper if we were started without
+        # an active window so that the animation is skipped
+        set_original_wallpaper "$wallpaper_mode"
+      fi
+      prev_blank="$blank"
+    fi
+
+    first_run=0
+    sleep "$POLL_INTERVAL"
+  done
+}
+
+show_help() {
+  echo "Usage: $BIN [-v|--verbose]"
+  echo ''
+  echo 'Options:'
+  echo '  -b, --blur N            set blur strength to N (4...128, default 32)'
+  echo '      --darken N          darken image by N (4...100, default 32)'
+  echo '      --lighten N         lengthen image by N (4...100, default 0)'
+  echo '  -c, --uncontrast        reduce contrast'
+  echo '      --save-image PATH   save blurred image to PATH'
+  echo '      --no-animate        skip fading animation'
+  echo ''
+  echo 'Daemon options:'
+  echo '  -d, --daemon            run in background'
+  echo '  -s, --stop              stop previously-ran daemon'
+  echo ''
+  echo 'Other options:'
+  echo '  -v, --verbose           show more messages'
+  echo '  -q, --quiet             supress messages'
+  echo ''
+}
+
+parse_opts() {
+  while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
+    -h | --help)
+      MODE=noop
+      show_help
+      ;;
+    -V | --version )
+      MODE=noop
+      echo version
+      ;;
+    -b | --blur )
+      shift
+      BLUR_STRENGTH="$1"
+      ;;
+    -c | --uncontrast )
+      REDUCE_CONTRAST=1
+      ;;
+    --save-image )
+      shift
+      BLUR_IMAGE_SAVE_LOCATION="$1"
+      ;;
+    --no-animate )
+      ANIMATE_FADE=0
+      ;;
+    -D | --dim | --darken )
+      shift
+      DIM_COLOR="#000"
+      DIM_STRENGTH="$1"
+      ;;
+    --lighten )
+      shift
+      DIM_COLOR="#fff"
+      DIM_STRENGTH="$1"
+      ;;
+    --tint-color )
+      shift
+      DIM_COLOR="$1"
+      ;;
+    --tint-strength )
+      shift
+      DIM_STRENGTH="$1"
+      ;;
+    -d | --daemon )
+      MODE=background
+      ;;
+    -s | --stop )
+      MODE=stop
+      ;;
+    -q | --quiet )
+      VERBOSE=0
+      ;;
+    -v | --verbose )
+      VERBOSE=2
+      ;;
+  esac; shift; done
+  if [[ "$1" == '--' ]]; then shift; fi
+}
+
+ensure_feh() {
+  if ! command -v feh >/dev/null; then
+    echo "$BIN requires Feh to set wallpapers."
+    exit
+  fi
+}
+
+# Ensure that 'graphicsmagick' is available.
+ensure_gm() {
+  if ! command -v gm >/dev/null; then
+    echo "$BIN requires GraphicsMagick to set wallpapers."
+    exit
+  fi
+}
+
+ensure_wmctrl() {
+  if ! command -v wmctrl >/dev/null; then
+    echo "$BIN requires wmctrl to detect events."
+    exit
+  fi
+}
+
+print_usage() {
+  head "Monitoring changes"
+  info "$BIN will now blur any wallpapers set using 'feh'."
+  info "To change your wallpaper, try:"
+  info ""
+  info "    feh --bg-tile your-image.jpg"
+}
+
+main() {
+  ensure_feh
+  ensure_gm
+  ensure_wmctrl
+  parse_opts "$@"
+
+  case "$MODE" in
+    background)
+      kill_other_instances
+      print_usage
+      "$0" --worker --quiet & disown
+
+      head "Background mode"
+      info "$BIN started in background mode!"
+      info "To stop, use '$BIN --stop'."
+      ;;
+  
+    stop)
+      kill_other_instances
+      ;;
+
+    noop)
+      exit
+      ;;
+
+    *)
+      kill_other_instances
+      print_usage
+      run_loop
+      ;;
+  esac
+}
+
+# Perform cleanup operations before stopping.
+do_cleanup () {
+  # Guard clause so that it's only ran once
+  if [[ "$CLEANING_UP" == "1" ]]; then return; fi
+  CLEANING_UP=1
+
+  rm -rf "$CACHE_DIR"
+
+  # Restore original wallpaper before exiting
+  if [[ "$MODE" == "foreground" ]] && [[ -e "$HOME/.fehbg" ]]; then
+    head "Restoring original wallpaper"
+    source "$HOME/.fehbg"
+  fi
+}
+
+finish () {
+  do_cleanup
+  exit 1
+}
+
+trap finish EXIT
+trap finish SIGHUP
+trap finish SIGINT
+trap finish SIGTERM
+main "$@"
diff --git a/home-bin/gify.sh b/home-bin/gify.sh
new file mode 100644 (file)
index 0000000..ee38bc0
--- /dev/null
@@ -0,0 +1,170 @@
+#! /bin/bash
+
+
+# ERROR & EXIT STATUSES
+SHOWHELP=61
+USERABORTED=62
+
+E_INTERROR=71
+E_NOOPTS=72
+E_NOARGS=73
+E_FILEXISTS=74
+E_NOIMAGES=75
+E_UNKNOWNOPT=76
+
+# TOOLS
+PWD=$(pwd)
+MOGRIFY=$(which mogrify)
+CONVERT=$(which convert)
+
+# we need mogrify and convert from the imagemagik toolset for this script to work
+if [[ ! -x $MOGRIFY || ! -x $CONVERT ]]; then
+    showerror missingdeps
+    exit $E_MISSINGDEPS
+fi
+
+
+# showhelp
+showhelp ()
+{
+case $1 in
+    resize )
+        echo "USAGE: $(basename $0) -r | --resize [width] [extension]"
+        ;;
+    gif )
+        echo "USAGE: $(basename $0) -g | --gif [delay] [extension] [output file name]"
+        ;;
+    * )
+                #|----------------------- TEXT MAX WIDTH - 80 CHARS ----------------------------|
+        echo -e "$(basename $0) - create animated gifs from images inside current directory"
+        echo -e "USAGE: $(basename $0) <option> [arguments]"
+        echo -e "\twhere <option> is one between:";echo
+        echo -e "\t-r | --resize [width] [extension]"
+        echo -e "\t\tresizes all the images matching the extension in the current folder to"
+        echo -e "\t\tthe width specified as argument.";echo
+        echo -e "\tg | --gif [delay] [extension] [output file name]"
+        echo -e "\t\tcreates the gif file using all the images in the current folder."
+        echo
+        echo -e "EXAMPLES:"
+        echo -e "$(basename $0) --resize 900 jpg"
+        echo -e "\twill resize all jpg images in the folder to 900px wide and mantain the"
+        echo -e "\taspect ratio of the original images"
+        echo
+        echo -e "$(basename $0) --gif 8 jpg france"
+        echo -e "\twill create a looping gif named france.gif using all the jpg files found"
+        echo -e "\tin the current folder and passing a tick delay of 8 between frames".
+        echo
+        ;;
+esac
+}
+
+# showerror
+showerror ()
+{
+    if [ -z $1 ];then
+        echo "INTERNAL ERROR - ABORTING"; echo
+        exit $E_INTERROR
+    fi
+    case $1 in
+        unknownopt)
+            echo "unknown option. Exiting."; echo
+        ;;
+        noopts)
+            echo "you didn't specify any options for the script to run. Exiting."; echo
+        ;;
+        noargs)
+            echo "you didn't specify any arguments for this option. Exiting."; echo
+        ;;
+        filexists)
+            echo "the file you want to write already exists. Exiting."; echo
+        ;;
+        noimages)
+            echo "at least two files must exist within $PWD with the"
+            echo "specified extension. Exiting"; echo
+        ;;
+        missingdeps)
+            echo "$(basename $0) requires both mogrify and convert from"
+            echo "the imagemagik tool suite. Install imagemagik using your"
+            echo "favourite package manager and then run this script again. Exiting."; echo
+    esac
+}
+
+##### MAIN #####
+if [ $# -eq 0 ];then
+    showerror noopts
+    showhelp
+    exit $E_NOOPTS
+else
+
+    while [ $# -gt 0 ];do
+        case $1 in
+            -h|--help)
+                showhelp
+                exit $SHOWHELP
+            ;;
+            -r|--resize)
+                WIDTH=$2
+                EXT=$3
+                shift
+                if [[ -z $WIDTH || -z $EXT ]];then
+                    showhelp resize
+                    showerror noargs
+                    exit $E_NOARGS
+                fi
+                IMAGES="$(ls -1 *.$EXT 2>/dev/null | wc -l)"
+                if [[ $IMAGES == 0 ]]; then
+                    showerror noimages
+                    exit $E_NOIMAGES
+                fi
+                clear
+                COUNT="$(ls -1 *.$EXT 2>/dev/null | wc -l)"
+                echo "you're going to resize all $COUNT .$EXT images inside $PWD at a fixed width of ${WIDTH}px"
+                read -p "do you wish to continue? [y/n] " -n 1 -r; echo
+                if [[ ! $REPLY =~ ^[Yy]$ ]]
+                then
+                    exit $USERABORTED
+                else
+                $MOGRIFY -resize $WIDTH *.$EXT
+                exit 0
+                fi
+            ;;
+            -g|--gif)
+                DELAY=$2
+                EXT=$3
+                OUTPUT=$4
+                shift
+                if [[ -z $DELAY || -z $EXT || -z $OUTPUT ]];then
+                    showhelp gif
+                    showerror noargs
+                    exit $E_NOARGS
+                elif [[ -f ${OUTPUT}.gif ]]; then
+                    showerror filexists
+                    exit $E_FILEXISTS
+                fi
+                IMAGES="$(ls -1 *.$EXT 2>/dev/null | wc -l)"
+                if [[ $IMAGES == 0 ]]; then
+                    showerror noimages
+                    exit $E_NOIMAGES
+                fi
+                clear
+                COUNT="$(ls -1 *.$EXT 2>/dev/null | wc -l)"
+                echo "you're going to create a looping gif named ${OUTPUT}.gif"
+                echo "out of all the $COUNT $EXT files inside $PWD with a tick"
+                echo "delay of $DELAY/100 of a second"; echo
+                read -p "do you wish to continue? [y/n] " -n 1 -r; echo
+                if [[ ! $REPLY =~ ^[Yy]$ ]]
+                then
+                    exit $USERABORTED
+                else
+                    $CONVERT -delay $DELAY *.$EXT -loop 0 ${OUTPUT}.gif
+                    exit 0
+                fi
+            ;;
+            *)
+                showerror unknownopt
+                showhelp
+                exit $E_UNKNOWNOPT
+        esac
+        shift
+    done
+fi
diff --git a/home-bin/hideIt.sh b/home-bin/hideIt.sh
new file mode 100644 (file)
index 0000000..9c925e2
--- /dev/null
@@ -0,0 +1,686 @@
+#!/usr/bin/env bash
+#
+#   Automagically hide/show a window by its name when the cursor is
+#   within a defined region or you mouse over it.
+#
+#   This script was initially written to imitate gnome-shell's systray
+#   but should be generic enough to do other things as well.
+#
+#   Requirements:
+#      bash, xdotool, xwininfo, xev
+#
+
+# Global variables used throughout the script
+WIN_ID=""
+WIN_NAME=""
+WIN_CLASS=""
+WIN_INSTANCE=""
+WAIT=1
+
+WIN_WIDTH=""
+WIN_HEIGHT=""
+WIN_POSX=""
+WIN_POSY=""
+
+SCREEN_WIDTH=""
+SCREEN_HEIGHT=""
+
+MINX=""
+MINY=""
+MAXX=""
+MAXY=""
+
+HOVER=1
+SIGNAL=1
+INTERVAL=1
+PEEK=3
+DIRECTION="left"
+STEPS=3
+NO_TRANS=1
+TOGGLE=1
+TOGGLE_PEEK=1
+
+_IS_HIDDEN=1
+_DOES_PEEK=0
+_HAS_REGION=1
+_WAIT_PID=""
+_PID_FILE=""
+
+
+usage() {
+    # Print usage
+    printf "usage: $0 [options]\n"
+    printf "\n"
+    printf "Required (At least on):\n"
+    printf " -N, --name [pattern]\n"
+    printf "   Match against the window name.\n"
+    printf "   This is the same string that is displayed in the window titlebar.\n"
+    printf "\n"
+    printf " -C, --class [pattern]\n"
+    printf "   Match against the window class.\n"
+    printf "\n"
+    printf " -I, --instance [pattern]\n"
+    printf "   Match against the window instance.\n"
+    printf "\n"
+    printf " --id [window-id]\n"
+    printf "   Explicitly specify a window id rather than searching for one.\n"
+    printf "\n"
+    printf "Optional:\n"
+    printf " -w, --wait\n"
+    printf "   Wait until a matching window was found.\n"
+    printf "   This will check once every second.\n"
+    printf "\n"
+    printf " -r, --region [posXxposY+offsetX+offsetY]\n"
+    printf "   Cursor region at which to trigger.\n"
+    printf "   Examples:\n"
+    printf "     --region 0x1080+10+-10 (Bottom left incl. a 10 pixel offset)\n"
+    printf "     --region 1920x1080+0+0 (Bottom right without offset)\n"
+    printf "\n"
+    printf " -H, --hover\n"
+    printf "   Show the window when hovering over it.\n"
+    printf "   If --region was defined, --hover will be ignored!\n"
+    printf "   This will only work if --peek is greater 0.\n"
+    printf "   By default, hover is off.\n"
+    printf "\n"
+    printf " -S, --signal\n"
+    printf "   Toggle the visibility by sending a 'SIGUSR1' signal.\n"
+    printf "   Both --region and --hover will be ignored.\n"
+    printf "\n"
+    printf " -i, --interval [interval]\n"
+    printf "   Interval in seconds to check the cursors location.\n"
+    printf "   Defaults to 1.\n"
+    printf "\n"
+    printf " -p, --peek [amount]\n"
+    printf "   When hidden, peek 'amount' of pixels to indicate the window.\n"
+    printf "   Required if --hover is used."
+    printf "   Defaults to 3.\n"
+    printf "\n"
+    printf " -d, --direction [left|right|top|bottom]\n"
+    printf "   direction in which to move the window.\n"
+    printf "   Defaults to left.\n"
+    printf "\n"
+    printf " -s, --steps [amount]\n"
+    printf "   steps in pixel used to move the window. The higher the value,\n"
+    printf "   the faster it will move at the cost of smoothness.\n"
+    printf "   Defaults to 3.\n"
+    printf "\n"
+    printf " -T, --no-trans\n"
+    printf "   Turn of the transition effect.\n"
+    printf "\n"
+    printf " -t, --toggle\n"
+    printf "   Send a SIGUSR1 signal to the process matching the same window.\n"
+    printf "   This will toggle the visibility of the window."
+    printf "\n\n"
+    printf " -P, --toggle-peek\n"
+    printf "   Send a SIGUSR2 signal to the process matching the same window.\n"
+    printf "   This will toggle the hidden state of the window if --peek is greater 0."
+    printf "\n\n"
+    printf "Examples:\n"
+    printf "  Dropdown Terminal:\n"
+    printf "    # Start a terminal with a unique name\n"
+    printf "    # (Make sure yourself it is positioned correctly)\n"
+    printf "    $ termite --title=dropdown-terminal &\n"
+    printf "\n"
+    printf "    # Hide it and wait for a SIGUSR1 signal\n"
+    printf "    $ hideIt.sh --name '^dropdown-terminal$' --direction top --steps 5 --signal\n"
+    printf "\n"
+    printf "    # Send a SIGUSR1 signal (This could be mapped to a keyboard shortcut)\n"
+    printf "    $ hideIt.sh --name '^dropdown-terminal$' --toggle\n"
+}
+
+
+argparse() {
+    # Parse system args
+
+    while [ $# -gt 0 ]; do
+        case $1 in
+            "-N"|"--name")
+                WIN_NAME="$2"
+                shift
+                ;;
+            "-C"|"--class")
+                WIN_CLASS="$2"
+                shift
+                ;;
+            "-I"|"--instance")
+                WIN_INSTANCE="$2"
+                shift
+                ;;
+            "--id")
+                if [[ ! $2 =~ [0-9]+ ]]; then
+                    printf "Invalid window id. Should be a number.\n" 1>&2
+                    exit 1
+                fi
+
+                WIN_ID="$2"
+                shift
+                ;;
+            "-w"|"--wait")
+                WAIT=0
+                ;;
+            "-H"|"--hover")
+                HOVER=0
+                ;;
+            "-S"|"--signal")
+                SIGNAL=0
+                ;;
+            "-r"|"--region")
+                local posX posY offsetX offsetY
+                read posX posY offsetX offsetY <<<$(echo "$2" | \
+                    sed -rn 's/^([0-9]+)x([0-9]+)\+(-?[0-9]+)\+(-?[0-9]+)/\1 \2 \3 \4/p')
+
+                # Test if we have proper values by trying
+                # to add them all together
+                expr $posX + $posY + $offsetX + $offsetY > /dev/null 2>&1
+                if [ $? -ne 0 ]; then
+                    printf "Invalid region. See --help for usage.\n" 1>&2
+                    exit 1
+                fi
+
+                MINX=$posX
+                MAXX=$((${MINX} + ${offsetX}))
+                if [ $MINX -gt $MAXX ]; then
+                    read MINX MAXX <<< "$MAXX $MINX"
+                fi
+
+                MINY=$posY
+                MAXY=$((${MINY} + ${offsetY}))
+                if [ $MINY -gt $MAXY ]; then
+                    read MINY MAXY <<< "$MAXY $MINY"
+                fi
+
+                if [[ ! $MINX =~ [0-9]+ ]] || [[ ! $MINY =~ [0-9]+ ]] \
+                        || [[ ! $MAXY =~ [0-9]+ ]] || [[ ! $MAXY =~ [0-9]+ ]]; then
+                    printf "Missing or invalid region. See --help for usage.\n" 1>&2
+                    exit 1
+                fi
+                _HAS_REGION=0
+                shift
+                ;;
+            "-i"|"--interval")
+                INTERVAL="$2"
+                if [[ ! $INTERVAL =~ [0-9]+ ]]; then
+                    printf "Interval should be a number. " 1>&2
+                    exit 1
+                fi
+                shift
+                ;;
+            "-p"|"--peek")
+                PEEK="$2"
+                if [[ ! $PEEK =~ [0-9]+ ]]; then
+                    printf "Peek should be a number. " 1>&2
+                    exit 1
+                fi
+                shift
+                ;;
+            "-d"|"--direction")
+                DIRECTION="$2"
+                if [[ ! "$DIRECTION" =~ ^(left|right|top|bottom)$ ]]; then
+                    printf "Invalid direction. See --help for usage.\n" 1>&2
+                    exit 1
+                fi
+                shift
+                ;;
+            "-s"|"--steps")
+                STEPS="$2"
+                if [[ ! $STEPS =~ [0-9]+ ]]; then
+                    printf "Steps should be a number. " 1>&2
+                    exit 1
+                fi
+                shift
+                ;;
+            "-T"|"--no-trans")
+                NO_TRANS=0
+                ;;
+            "-t"|"--toggle")
+                TOGGLE=0
+                ;;
+            "-P"|"--toggle-peek")
+                TOGGLE_PEEK=0
+                ;;
+            "-h"|"--help")
+                usage
+                exit 0
+                ;;
+            **)
+                printf "Didn't understand '$1'\n" 1>&2
+                printf "See --help for usage.\n"
+                exit 1
+                ;;
+        esac
+        shift
+    done
+
+    # Check required arguments
+    local _names="${WIN_ID}${WIN_NAME}${WIN_CLASS}${WIN_INSTANCE}"
+    if [ -z "$_names" ] && [ -z "$WIN_ID" ]; then
+        printf "At least one of --name, --class, --instance or --id" 1>&2
+        printf " is required!\n" 1>&2
+        exit 1
+    fi
+
+    if [ $TOGGLE -ne 0 ] && [ $TOGGLE_PEEK -ne 0 ] && [ $SIGNAL -ne 0 ] \
+            && [ $_HAS_REGION -ne 0 ] && [ $HOVER -ne 0 ]; then
+        printf "At least one of --toggle, --signal, --hover or" 1>&2
+        printf " --region is required!\n" 1>&2
+        exit 1
+    fi
+}
+
+
+function fetch_window_id() {
+    # Sets the values for the following global
+    #   WIN_ID
+
+    # We already have a window id
+    if [ ! -z "$WIN_ID" ]; then
+        _PID_FILE="/tmp/hideIt-${WIN_ID}.pid"
+        return
+    fi
+
+    local _id=-1
+
+    # Search all windows matching the provided class
+    local _tmp1=()
+    if [ ! -z "$WIN_CLASS" ]; then
+        _tmp1=($(xdotool search --class "$WIN_CLASS"))
+        _tmp1=${_tmp1:--1}
+    fi
+
+    # Search all windows matching the provided instance
+    local _tmp2=()
+    if [ ! -z "$WIN_INSTANCE" ]; then
+        _tmp2=($(xdotool search --classname "$WIN_INSTANCE"))
+        _tmp2=${_tmp2:--1}
+    fi
+
+    # Search all windows matching the provided name (title)
+    local _tmp3=()
+    if [ ! -z "$WIN_NAME" ]; then
+        _tmp3=($(xdotool search --name "$WIN_NAME"))
+        _tmp3=${_tmp3:--1}
+    fi
+
+    # Shift values upwards
+    for i in {1..2}; do
+        if [ -z $_tmp1 ]; then
+            _tmp1=(${_tmp2[@]})
+            _tmp2=()
+        fi
+
+        if [ -z $_tmp2 ]; then
+            _tmp2=(${_tmp3[@]})
+            _tmp3=()
+        fi
+    done
+
+    if [ -z $_tmp2 ]; then
+        # We only have one list of ids so we pick the first one from it
+        _id=${_tmp1[0]}
+    else
+        # We have multiple lists so we have to find the id that appears
+        # in all of them
+        local _oldIFS=$IFS
+        IFS=$'\n\t'
+
+        local _ids=($(comm -12 \
+            <(echo "${_tmp1[*]}" | sort) \
+            <(echo "${_tmp2[*]}" | sort)))
+
+        if [ ! -z $_tmp3 ]; then
+            _ids=($(comm -12 \
+                <(echo "${_tmp3[*]}" | sort) \
+                <(echo "${_ids[*]}" | sort)))
+        fi
+        IFS=$_oldIFS
+
+        _id=${_ids[0]}
+    fi
+
+    if [[ $_id =~ [0-9]+ ]] && [ $_id -gt 0 ]; then
+        WIN_ID=$_id
+        _PID_FILE="/tmp/hideIt-${WIN_ID}.pid"
+    fi
+}
+
+
+function fetch_screen_dimensions() {
+    # Sets the values for the following globals
+    #    SCREEN_WIDTH, SCREEN_HEIGHT
+
+    local win_info=$(xwininfo -root)
+    SCREEN_WIDTH=$(echo "$win_info" | sed -rn 's/.*Width: +([0-9]+)/\1/p')
+    SCREEN_HEIGHT=$(echo "$win_info" | sed -rn 's/.*Height: +([0-9]+)/\1/p')
+}
+
+
+function fetch_window_dimensions() {
+    # Sets the values for the following globals unless no WIN_ID exists
+    #    WIN_WIDTH, WIN_HEIGHT, WIN_POSX, WIN_POSY
+
+    if [[ ! $WIN_ID =~ [0-9]+ ]]; then
+        return
+    fi
+
+    local win_info=$(xwininfo -id $WIN_ID)
+
+    WIN_WIDTH=$(echo "$win_info" | sed -rn 's/.*Width: +([0-9]+)/\1/p')
+    WIN_HEIGHT=$(echo "$win_info" | sed -rn 's/.*Height: +([0-9]+)/\1/p')
+
+    if [ ! -z "$1" ] && [ $1 -eq 0 ]; then
+        WIN_POSX=$(echo "$win_info" | \
+            sed -rn 's/.*Absolute upper-left X: +(-?[0-9]+)/\1/p')
+        WIN_POSY=$(echo "$win_info" | \
+            sed -rn 's/.*Absolute upper-left Y: +(-?[0-9]+)/\1/p')
+    fi
+}
+
+
+function send_signal() {
+    # Send a SIGUSR1 to an active hideIt.sh instance
+    # if a pid file was found.
+    local signal=$1
+    if [ ! -f "$_PID_FILE" ]; then
+        printf "Pid file at \"${_PID_FILE}\" doesn't exist!\n" 1>&2
+        exit 1
+    fi
+
+    local _pid=`cat $_PID_FILE`
+    printf "Sending ${signal} to instance...\n"
+
+    if [[ $_pid =~ [0-9]+ ]]; then
+        kill -${signal} $_pid
+        exit 0
+    else
+        printf "Invalid pid in \"${_PID_FILE}\".\n" 1>&2
+        exit 1
+    fi
+}
+
+
+function hide_window() {
+    # Move the window in or out
+    # Args:
+    #     hide: 0 to hide, 1 to show
+
+    local hide=$1
+
+    # Make sure window still exists and exit if not.
+    xwininfo -id $WIN_ID &> /dev/null
+    if [ $? -ne 0 ]; then
+        printf "Window doesn't exist anymore, exiting!\n"
+        exit 0
+    fi
+
+    _IS_HIDDEN=$hide
+
+    # Update WIN_WIDTH, WIN_HEIGHT in case they changed
+    fetch_window_dimensions
+
+    # Activate the window.
+    # Should bring it to the front, change workspace etc.
+    if [ $hide -ne 0 ]; then
+        xdotool windowactivate $WIN_ID > /dev/null 2>&1
+    fi
+
+    # Generate the sequence used to move the window
+    local to=()
+    local sequence=()
+    if [ "$DIRECTION" == "left" ]; then
+        to=-$(($WIN_WIDTH - $PEEK))
+        if [ $hide -eq 0 ]; then
+            sequence=($(seq $WIN_POSX -$STEPS $to))
+            sequence+=($to)
+        else
+            sequence=($(seq $to $STEPS $WIN_POSX))
+            sequence+=($WIN_POSX)
+        fi
+
+    elif [ "$DIRECTION" == "right" ]; then
+        to=$(($SCREEN_WIDTH - $PEEK))
+        if [ $hide -eq 0 ]; then
+            sequence=($(seq $WIN_POSX $STEPS $to))
+            sequence+=($to)
+        else
+            sequence=($(seq $to -$STEPS $WIN_POSX))
+            sequence+=($WIN_POSX)
+        fi
+
+    elif [ "$DIRECTION" == "bottom" ]; then
+        to=$(($SCREEN_HEIGHT - $PEEK))
+        if [ $hide -eq 0 ]; then
+            sequence=($(seq $WIN_POSY $STEPS $to))
+            sequence+=($to)
+        else
+            sequence=($(seq $to -$STEPS $WIN_POSY))
+            sequence+=($WIN_POSY)
+        fi
+
+    elif [ "$DIRECTION" == "top" ]; then
+        to=-$(($WIN_HEIGHT - $PEEK))
+        if [ $hide -eq 0 ]; then
+            sequence=($(seq $WIN_POSY -$STEPS $to))
+            sequence+=($to)
+        else
+            sequence=($(seq $to $STEPS $WIN_POSY))
+            sequence+=($WIN_POSY)
+        fi
+    fi
+
+    # Actually move the window
+    if [ $NO_TRANS -ne 0 ]; then
+        for pos in ${sequence[@]}; do
+            if [[ "$DIRECTION" =~ ^(left|right)$ ]]; then
+                xdotool windowmove $WIN_ID $pos $WIN_POSY
+            elif [[ "$DIRECTION" =~ ^(top|bottom)$ ]]; then
+                xdotool windowmove $WIN_ID $WIN_POSX $pos
+            fi
+        done
+    else
+        pos=${sequence[-1]}
+        if [[ "$DIRECTION" =~ ^(left|right)$ ]]; then
+            xdotool windowmove $WIN_ID $pos $WIN_POSY
+        elif [[ "$DIRECTION" =~ ^(top|bottom)$ ]]; then
+            xdotool windowmove $WIN_ID $WIN_POSX $pos
+        fi
+    fi
+
+    # In case we hid the window, try to give focus to whatever is
+    # underneath the cursor.
+    if [ $hide -eq 0 ]; then
+        eval $(xdotool getmouselocation --shell)
+        xdotool windowactivate $WINDOW > /dev/null 2>&1
+    fi
+}
+
+
+function toggle() {
+    # Toggle the hidden state of the window
+
+    if [ $_IS_HIDDEN -eq 0 ]; then
+        hide_window 1
+    else
+        hide_window 0
+    fi
+}
+
+function toggle_peek() {
+    # Completely hide/unhide the window in case PEEK is greater 0
+
+    if [ $PEEK -eq 0 ]; then
+        return
+    fi
+
+    local _peek=$PEEK
+    local _win_posx=$WIN_POSX
+    local _win_posy=$WIN_POSY
+
+    fetch_window_dimensions 0
+
+    if [ $_DOES_PEEK -eq 0 ]; then
+        _DOES_PEEK=1
+        PEEK=0
+    else
+        _DOES_PEEK=0
+    fi
+
+    hide_window 0
+
+    PEEK=$_peek
+    WIN_POSX=$_win_posx
+    WIN_POSY=$_win_posy
+}
+
+function serve_region() {
+    # Check the cursors location and act accordingly
+
+    local _hide=0
+    while true; do
+        if [ $_DOES_PEEK -eq 0 ]; then
+            # Get cursor x, y position and active window
+            eval $(xdotool getmouselocation --shell)
+
+            # Test if the cursor is within the region
+            if [ $X -ge $MINX -a $X -le $MAXX ] \
+                    && [ $Y -ge $MINY -a $Y -le $MAXY ]; then
+                _hide=1
+            else
+                _hide=0
+            fi
+
+            # Don't hide if the cursor is still above the window
+            if [ $_IS_HIDDEN -ne 0 ] \
+                    && [ $_hide -eq 0 ] \
+                    && [ $WINDOW -eq $WIN_ID ]; then
+                _hide=1
+            fi
+
+            # Only do something if necessary
+            if [ $_IS_HIDDEN -ne $_hide ]; then
+                hide_window $_hide
+            fi
+        fi
+
+        # Cut some slack
+        sleep $INTERVAL
+    done
+}
+
+
+function serve_xev() {
+    # Wait for cursor "Enter" and "Leave" events reported by
+    # xev and act accordingly
+
+    xev -id $WIN_ID -event mouse | while read line; do
+        if [[ "$line" =~ ^EnterNotify.* ]]; then
+            hide_window 1
+        elif [[ "$line" =~ ^LeaveNotify.* ]]; then
+            hide_window 0
+        fi
+    done
+}
+
+
+function restore() {
+    # Called by trap once we receive an EXIT
+
+    if [ -n "$_WAIT_PID" ]; then
+        kill -- "-${_WAIT_PID}"
+    fi
+
+    if [ -f "$_PID_FILE" ]; then
+        rm "$_PID_FILE"
+    fi
+
+    if [ $_IS_HIDDEN -eq 0 ]; then
+        printf "Restoring original window position...\n"
+        hide_window 1
+    fi
+
+    exit 0
+}
+
+
+function main() {
+    # Entry point for hideIt
+
+    # Parse all the args!
+    argparse "$@"
+
+    printf "Searching window...\n"
+    fetch_window_id
+
+    # If enabled, wait until a window was found.
+    if [ $WAIT -eq 0 ] && [[ ! $WIN_ID =~ [0-9]+ ]]; then
+        printf "Waiting for window"
+        while [[ ! $WIN_ID =~ [0-9]+ ]]; do
+            printf "."
+            fetch_window_id
+            sleep 1
+        done
+        printf "\n"
+    fi
+
+    if [[ ! $WIN_ID =~ [0-9]+ ]]; then
+        printf "No window found!\n" 1>&2
+        exit 1
+    else
+        printf "Found window with id: $WIN_ID\n"
+    fi
+
+    if [ $TOGGLE -eq 0 ]; then
+        send_signal SIGUSR1
+        exit 0
+    fi
+
+    if [ $TOGGLE_PEEK -eq 0 ]; then
+        send_signal SIGUSR2
+        exit 0
+    fi
+
+    printf "Fetching window dimensions...\n"
+    fetch_window_dimensions 0
+
+    printf "Fetching screen dimensions...\n"
+    fetch_screen_dimensions
+
+    trap restore EXIT
+
+    printf "Initially hiding window...\n"
+    hide_window 0
+
+    # Save our pid into a file
+    echo "$$" > /tmp/hideIt-${WIN_ID}.pid
+    trap toggle_peek SIGUSR2
+
+    # Start observing
+    if [ $_HAS_REGION -eq 0 ]; then
+        printf "Defined region:\n"
+        printf "  X: $MINX $MAXX\n"
+        printf "  Y: $MINY $MAXY\n"
+        printf "\n"
+        printf "Waiting for region...\n"
+        serve_region &
+        _WAIT_PID=$!
+    elif [ $SIGNAL -eq 0 ]; then
+        printf "Waiting for SIGUSR1...\n"
+        trap toggle SIGUSR1
+        sleep infinity &
+        _WAIT_PID=$!
+    elif [ $HOVER -eq 0 ]; then
+        printf "Waiting for HOVER...\n"
+        serve_xev &
+        _WAIT_PID=$!
+    fi
+
+    if [ -n "$_WAIT_PID" ]; then
+        while true; do
+            wait "$_WAIT_PID"
+            printf "Received signal...\n"
+        done
+    fi
+}
+
+# Lets do disss!
+set -m
+main "$@"
diff --git a/home-bin/hidebars.sh b/home-bin/hidebars.sh
new file mode 100644 (file)
index 0000000..fe3a494
--- /dev/null
@@ -0,0 +1,10 @@
+#! /bin/bash
+
+# kill all previous instances of hideIt
+kill $(cat /tmp/hideIt-*.pid)
+
+#hide bars
+hideIt.sh --name "^polybar-top_VGA-1$" --hover --peek 3 --direction top --steps 25 & disown
+hideIt.sh --name "^polybar-bottom_VGA-1$" --hover --peek 3 --direction bottom --steps 25 & disown
+hideIt.sh --name "^stalonetray$" --region 0x120+0+300 --peek 3 --direction left --steps 25 & disown
+
diff --git a/home-bin/i3-exit b/home-bin/i3-exit
new file mode 100644 (file)
index 0000000..13858ff
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+rofi_command="rofi -theme darknix/i3exit.rasi"
+
+NO="ﰸ"
+YES=""
+
+options="$YES\n$NO"
+chosen="$(echo -e "$options" | $rofi_command -dmenu -selected-row 1)"
+case $chosen in
+    "$YES")
+               # take wallpaper.sh down with you
+               for pid in $(cat /tmp/wallpaper.pid); do
+                       # sending all term signals. hopefully one of them will work!
+                       kill -15 $pid
+                       kill -1 $pid
+                       kill -9 $pid
+               done
+               rm /tmp/wallpaper.pid
+        i3-msg exit
+        ;;
+    "$NO")
+        exit 0
+        ;;
+esac
diff --git a/home-bin/i3-scrot b/home-bin/i3-scrot
new file mode 100644 (file)
index 0000000..57958f0
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+# /usr/bin/i3-scrot
+#
+# simple screenshot-script using scrot for manjaro-i3 by oberon@manjaro.org
+
+_conf=$HOME/.config/i3-scrot.conf
+
+if ! [ -f $_conf ]; then
+       echo "scrot_dir=$(xdg-user-dir PICTURES)" > $_conf
+fi
+
+source $_conf
+
+if ! [ -d $scrot_dir ]; then
+       mkdir -p $scrot_dir
+fi
+
+if ! [[ -z "$2" ]]; then
+    cmd="scrot -d $2"
+else
+    cmd='scrot'
+fi
+
+case "$1" in
+       --desk|-d|$NULL)
+               cd $scrot_dir
+           $cmd
+               sleep 1
+               notify-send "screenshot has been saved in $scrot_dir"
+               ;;
+       --select|-s)
+               cd $scrot_dir
+               notify-send 'select a window or drag to select an area for the screenshot' &
+               $cmd -s
+               sleep 1
+               notify-send "screenshot has been saved in $scrot_dir"
+               ;;
+       --help|-h)
+               echo "
+available options:
+-d | --desk    full screen
+-s | --select  selection
+-h | --help    display this information
+
+All options can be used with a delay
+by adding the number of seconds, like for example:
+'i3-scrot -d 5'
+
+Default option is 'full screen'.
+
+The file destination can be set in ${_conf}.
+Default is $scrot_dir
+"
+               ;;
+       *)
+               echo "
+== ! i3-scrot: missing or wrong argument ! ==
+
+available options:
+-d | --desk    full screen
+-s | --select  selection
+-h | --help    display this information
+
+Default option is 'full screen'.
+
+The file destination can be set in ${_conf}.
+Default is $scrot_dir
+"
+
+        exit 2
+esac
+
+exit 0
diff --git a/home-bin/i3lock-blur b/home-bin/i3lock-blur
new file mode 100644 (file)
index 0000000..1156bcc
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/bash
+TMPBG=/tmp/screen.png
+LOCK=~/.round.face
+RES=$(xrandr | grep 'current' | sed -E 's/.*current\s([0-9]+)\sx\s([0-9]+).*/\1x\2/')
+ffmpeg -f x11grab -video_size $RES -y -i $DISPLAY -i $LOCK -filter_complex "boxblur=5:1,overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2" -vframes 1 $TMPBG -loglevel quiet
+i3lock -u -i $TMPBG
+rm $TMPBG
diff --git a/home-bin/i3lock-fancy b/home-bin/i3lock-fancy
new file mode 100644 (file)
index 0000000..4e393c7
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+# Author: Dolores Portalatin <hello@doloresportalatin.info>
+# Dependencies: imagemagick, i3lock-color-git, scrot, wmctrl (optional)
+set -o errexit -o noclobber -o nounset
+
+hue=(-level "0%,100%,0.6")
+effect=(-filter Gaussian -resize 20% -define "filter:sigma=1.5" -resize 500.5%)
+# default system sans-serif font
+font=$(magick convert -list font | awk "{ a[NR] = \$2 } /family: $(fc-match sans -f "%{family}\n")/ { print a[NR-1]; exit }")
+image=$(mktemp --suffix=.png)
+shot=(import -silent -window root)
+desktop=""
+i3lock_cmd=(i3lock -i "$image")
+shot_custom=false
+
+options="Options:
+    -h, --help       This help menu.
+
+    -d, --desktop    Attempt to minimize all windows before locking.
+
+    -g, --greyscale  Set background to greyscale instead of color.
+
+    -p, --pixelate   Pixelate the background instead of blur, runs faster.
+
+    -f <fontname>, --font <fontname>  Set a custom font.
+
+    -t <text>, --text <text> Set a custom text prompt.
+
+    -l, --listfonts  Display a list of possible fonts for use with -f/--font.
+                     Note: this option will not lock the screen, it displays
+                     the list and exits immediately.
+
+    -n, --nofork     Do not fork i3lock after starting.
+
+    --               Must be last option. Set command to use for taking a
+                     screenshot. Default is 'import -window root'. Using 'scrot'
+                     or 'maim' will increase script speed and allow setting
+                     custom flags like having a delay."
+
+# move pipefail down as for some reason "convert -list font" returns 1
+set -o pipefail
+trap 'rm -f "$image"' EXIT
+temp="$(getopt -o :hdnpglt:f: -l desktop,help,listfonts,nofork,pixelate,greyscale,text:,font: --name "$0" -- "$@")"
+eval set -- "$temp"
+
+# l10n support
+case "${LANG:-}" in
+    af_* ) text="Tik wagwoord om te ontsluit" ;; # Afrikaans
+    cs_* ) text="Pro odemčení zadajte heslo" ;; # Czech
+    de_* ) text="Bitte Passwort eingeben" ;; # Deutsch
+    da_* ) text="Indtast adgangskode" ;; # Danish
+    en_* ) text="Type password to unlock" ;; # English
+    es_* ) text="Ingrese su contraseña" ;; # Española
+    fr_* ) text="Entrez votre mot de passe" ;; # Français
+    he_* ) text="הקלד סיסמה לביטול הנעילה" ;; # Hebrew עברית
+    hi_* ) text="अनलॉक करने के लिए पासवर्ड टाईप करें" ;; #Hindi
+    id_* ) text="Masukkan kata sandi Anda" ;; # Bahasa Indonesia
+    it_* ) text="La sai?" ;; # Italian
+    ja_* ) text="パスワードを入力してください" ;; # Japanese
+    lv_* ) text="Ievadi paroli" ;; # Latvian
+    nb_* ) text="Skriv inn passord" ;; # Norwegian
+    pl_* ) text="Podaj hasło" ;; # Polish
+    pt_* ) text="Digite a senha para desbloquear" ;; # Português
+    sk_* ) text="Pre odomknutie zadajte heslo" ;; # Slovak
+    tr_* ) text="Giriş yapmak için şifrenizi girin" ;; # Turkish
+    ru_* ) text="Введите пароль" ;; # Russian
+    zh_* ) text="请输入密码以解锁" ;; # Chinese
+    * ) text="Type password to unlock" ;; # Default to English
+esac
+
+while true ; do
+    case "$1" in
+        -h|--help)
+            printf "Usage: %s [options]\n\n%s\n\n" "${0##*/}" "$options"; exit 1 ;;
+        -d|--desktop) desktop=$(command -V wmctrl) ; shift ;;
+        -g|--greyscale) hue=(-level "0%,100%,0.6" -set colorspace Gray -average) ; shift ;;
+        -p|--pixelate) effect=(-scale 10% -scale 1000%) ; shift ;;
+        -f|--font)
+            case "$2" in
+                "") shift 2 ;;
+                *) font=$2 ; shift 2 ;;
+            esac ;;
+        -t|--text) text=$2 ; shift 2 ;;
+        -l|--listfonts)
+           magick convert -list font | awk -F: '/Font: / { print $2 }' | sort -du | command -- ${PAGER:-less}
+           exit 0 ;;
+       -n|--nofork) i3lock_cmd+=(--nofork) ; shift ;;
+        --) shift; shot_custom=true; break ;;
+        *) echo "error" ; exit 1 ;;
+    esac
+done
+
+if "$shot_custom" && [[ $# -gt 0 ]]; then
+    shot=("$@");
+fi
+
+command -- "${shot[@]}" "$image"
+
+value="60" #brightness value to compare to
+
+color=$(magick convert "$image" -gravity center -crop 100x100+0+0 +repage -colorspace hsb \
+    -resize 1x1 txt:- | awk -F '[%$]' 'NR==2{gsub(",",""); printf "%.0f\n", $(NF-1)}');
+
+if [[ $color -gt $value ]]; then #white background image and black text
+    bw="black"
+#    icon="/usr/share/i3lock-fancy/icons/lockdark.png"
+    icon="/home/danix/.round.face"
+    param=("--inside-color=0000001c" "--ring-color=0000003e" \
+        "--line-color=00000000" "--keyhl-color=0000ff80" "--ringver-color=ffffff00" \
+        "--separator-color=22222260" "--insidever-color=ffffff1c" \
+        "--ringwrong-color=ffffff55" "--insidewrong-color=ffffff1c" \
+        "--verif-color=ffffff00" "--wrong-color=ff000000" "--time-color=ffffff00" \
+        "--date-color=ffffff00" "--layout-color=ffffff00")
+else #black
+    bw="white"
+#    icon="/usr/share/i3lock-fancy/icons/lock.png"
+    icon="/home/danix/.round.face"
+    param=("--inside-color=ffffff1c" "--ring-color=ffffff3e" \
+        "--line-color=ffffff00" "--keyhl-color=0000ff80" "--ringver-color=00000000" \
+        "--separator-color=22222260" "--insidever-color=0000001c" \
+        "--ringwrong-color=00000055" "--insidewrong-color=0000001c" \
+        "--verif-color=00000000" "--wrong-color=ff000000" "--time-color=00000000" \
+        "--date-color=00000000" "--layout-color=00000000")
+fi
+
+magick convert "$image" "${hue[@]}" "${effect[@]}" -font "$font" -pointsize 26 -fill "$bw" -gravity center \
+    -annotate +0+160 "$text" "$icon" -gravity center -composite "$image"
+
+# If invoked with -d/--desktop, we'll attempt to minimize all windows (ie. show
+# the desktop) before locking.
+${desktop} ${desktop:+-k on}
+
+# try to use i3lock with prepared parameters
+if ! "${i3lock_cmd[@]}" "${param[@]}" >/dev/null 2>&1; then
+    # We have failed, lets get back to stock one
+    "${i3lock_cmd[@]}"
+fi
+
+# As above, if we were passed -d/--desktop, we'll attempt to restore all windows
+# after unlocking.
+${desktop} ${desktop:+-k off}
diff --git a/home-bin/i3suspend b/home-bin/i3suspend
new file mode 100644 (file)
index 0000000..410cc07
--- /dev/null
@@ -0,0 +1,7 @@
+#! /bin/sh
+
+i3lock-fancy &
+sleep 3
+dbus-send --system --print-reply --dest="org.freedesktop.UPower" /org/freedesktop/UPower org.freedesktop.UPower.Suspend
+exit
+
diff --git a/home-bin/info-airqualityindex.sh b/home-bin/info-airqualityindex.sh
new file mode 100644 (file)
index 0000000..3aebddf
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+TOKEN="78eaad275bf877526d8888ea01e18adcddc0b85d"
+CITY="Verona"
+
+API="https://api.waqi.info/feed"
+
+if [ -n "$CITY" ]; then
+    aqi=$(curl -sf "$API/$CITY/?token=$TOKEN")
+else
+    location=$(curl -sf https://location.services.mozilla.com/v1/geolocate?key=geoclue)
+
+    if [ -n "$location" ]; then
+        location_lat="$(echo "$location" | jq '.location.lat')"
+        location_lon="$(echo "$location" | jq '.location.lng')"
+
+        aqi=$(curl -sf "$API/geo:$location_lat;$location_lon/?token=$TOKEN")
+    fi
+fi
+
+if [ -n "$aqi" ]; then
+    if [ "$(echo "$aqi" | jq -r '.status')" = "ok" ]; then
+        aqi=$(echo "$aqi" | jq '.data.aqi')
+
+        if [ "$aqi" -lt 50 ]; then
+            echo "%{F#009966}󰌪 %{F-} $aqi"
+        elif [ "$aqi" -lt 100 ]; then
+            echo "%{F#ffde33}󰌪 %{F-} $aqi"
+        elif [ "$aqi" -lt 150 ]; then
+            echo "%{F#ff9933}󰌪 %{F-} $aqi"
+        elif [ "$aqi" -lt 200 ]; then
+            echo "%{F#cc0033}󰌪 %{F-} $aqi"
+        elif [ "$aqi" -lt 300 ]; then
+            echo "%{F#660099}󰌪 %{F-} $aqi"
+        else
+            echo "%{F#7e0023}󰌪 %{F-} $aqi"
+        fi
+    else
+        echo "$aqi" | jq -r '.data'
+    fi
+fi
diff --git a/home-bin/is_installed b/home-bin/is_installed
new file mode 100644 (file)
index 0000000..70df65a
--- /dev/null
@@ -0,0 +1,123 @@
+#! /bin/bash
+#
+# is_installed - a script to find if a package
+# is currently installed on Slackware and derivates
+
+# var
+SHOWHELP=69
+E_NOPKGFOUND=70
+E_NOARGS=71
+PKGS_PATH="/var/log/packages"
+
+PROG=$(basename $0)
+
+
+# colors
+RED='\033[1;31m'
+ORANGE='\033[1;33m'
+GREEN='\033[1;32m'
+MAGENTA='\033[1;35m'
+NC='\033[0m'
+
+function search_file () {
+       SEARCH=$1
+       if [ -z $1 ]; then
+               #we don't have a string to search
+               echo -e "Usage: ${MAGENTA}$PROG ${NC}[file-search] ${GREEN}< filename >"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOARGS
+       fi
+       finding=$(grep -Hi "$SEARCH" ${PKGS_PATH}/*)
+       if [[ ! -z $finding ]]; then
+               old_IFS=$IFS
+               IFS=$'\n'
+               old_pkgname=""
+               for line in $finding; do
+                       pkgname=$(echo $line | cut -d":" -f 1)
+                       match=$(echo $line | cut -d":" -f 2)
+                       if [[ $pkgname != $old_pkgname ]]; then
+                               echo -e "${ORANGE}$(basename $pkgname)${NC}"
+                       fi
+                       old_pkgname=$pkgname
+                       echo -en "\t${GREEN}$match${NC}\n"
+               done
+               IFS=${old_IFS}
+       else
+               echo -e "${ORANGE}no match for pattern ${GREEN}'$SEARCH' ${ORANGE}found"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOPKGFOUND
+       fi
+}
+
+function search_package () {
+       SEARCH=$1
+       if [ -z $1 ]; then
+               #we don't have a string to search
+               echo -e "Usage: ${MAGENTA}$PROG ${GREEN}<filename>"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOARGS
+       fi
+       finding=$(/bin/ls -1 $PKGS_PATH |grep -i "$SEARCH")
+       if [[ ! -z $finding ]]; then
+               echo -en "${GREEN}$finding\n"
+               echo -e "${NC}"
+       else
+               echo -e "${ORANGE}no package matching ${GREEN}'$SEARCH' ${ORANGE}found"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOPKGFOUND
+       fi
+}
+
+function info () {
+       SEARCH=$1
+       if [ -z $1 ]; then
+               #we don't have a string to search
+               echo -e "Usage: ${MAGENTA}$PROG ${ORANGE}info ${GREEN}<filename>"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOARGS
+       fi
+       finding=$(/bin/ls -1 $PKGS_PATH |grep -i "$SEARCH")
+       if [[ ! -z $finding ]]; then
+               less $PKGS_PATH/$finding
+       else
+               echo -e "${ORANGE}no package matching ${GREEN}'$SEARCH' ${ORANGE}found"
+               echo -e "${RED}exiting${NC}"
+               exit $E_NOPKGFOUND
+       fi
+}
+
+function help () {
+       echo
+       echo -e "${MAGENTA}$PROG${NC} -\tSearch for installed packages in the system"
+       echo -e "\t\tand show which package contains a specific file."
+       echo -e "${GREEN}USAGE:"
+       echo -e "\t${MAGENTA}$PROG ${ORANGE}[ file-search ] [ help ] ${GREEN}< item >"
+       echo -e "\t${MAGENTA}$PROG ${GREEN}<item>${NC} - Search between all installed packages for ${GREEN}item${NC}."
+       echo -e "\t${MAGENTA}$PROG ${ORANGE}file-search ${GREEN}<item>${NC} - Search for ${GREEN}item${NC} inside every package installed."
+       echo -e "\t${MAGENTA}$PROG ${ORANGE}help${NC} - Show this help and exit."
+       echo -e "${NC}"
+}
+# controlla se vengono passati parametri
+if [ -z $1 ]; then
+       help
+       exit $E_NOARGS
+else
+       case $1 in
+               "file-search" )
+                       search_file $2
+                       ;;
+               "info" )
+                       info $2
+                       ;;
+               "help" )
+                       help
+                       exit $SHOWHELP
+                       ;;
+               * )
+                       search_package $1
+                       ;;
+       esac
+fi
+
+
+exit
diff --git a/home-bin/lightsOn.sh b/home-bin/lightsOn.sh
new file mode 100644 (file)
index 0000000..89fed42
--- /dev/null
@@ -0,0 +1,240 @@
+#!/bin/bash
+# lightsOn.sh
+
+# Copyright (c) 2013 iye.cba at gmail com
+# url: https://github.com/iye/lightsOn
+# This script is licensed under GNU GPL version 2.0 or above
+
+# Description: Bash script that prevents the screensaver and display power
+# management (DPMS) to be activated when you are watching Flash Videos
+# fullscreen on Firefox and Chromium.
+# Can detect mplayer, minitube, and VLC when they are fullscreen too.
+# Also, screensaver can be prevented when certain specified programs are running.
+# lightsOn.sh needs xscreensaver or kscreensaver to work.
+
+
+# HOW TO USE: Start the script with the number of seconds you want the checks
+# for fullscreen to be done. Example:
+# "./lightsOn.sh 120 &" will Check every 120 seconds if Mplayer, Minitube
+# VLC, Firefox or Chromium are fullscreen and delay screensaver and Power Management if so.
+# You want the number of seconds to be ~10 seconds less than the time it takes
+# your screensaver or Power Management to activate.
+# If you don't pass an argument, the checks are done every 50 seconds.
+#
+# An optional array variable exists here to add the names of programs that will delay the screensaver if they're running.
+# This can be useful if you want to maintain a view of the program from a distance, like a music playlist for DJing,
+# or if the screensaver eats up CPU that chops into any background processes you have running,
+# such as realtime music programs like Ardour in MIDI keyboard mode.
+# If you use this feature, make sure you use the name of the binary of the program (which may exist, for instance, in /usr/bin).
+
+
+# Modify these variables if you want this script to detect if Mplayer,
+# VLC, Minitube, or Firefox or Chromium Flash Video are Fullscreen and disable
+# xscreensaver/kscreensaver and PowerManagement.
+mplayer_detection=1
+vlc_detection=0
+firefox_flash_detection=1
+chromium_flash_detection=0
+minitube_detection=0
+
+# Names of programs which, when running, you wish to delay the screensaver.
+delay_progs=('duckstation-qt') # For example ('ardour2' 'gmpc')
+
+
+# YOU SHOULD NOT NEED TO MODIFY ANYTHING BELOW THIS LINE
+
+
+# enumerate all the attached screens
+displays=""
+while read id
+do
+    displays="$displays $id"
+done < <(xvinfo | sed -n 's/^screen #\([0-9]\+\)$/\1/p')
+
+
+# Detect screensaver been used (xscreensaver, kscreensaver or none)
+screensaver=`pgrep -l xscreensaver | grep -wc xscreensaver`
+if [ $screensaver -ge 1 ]; then
+    screensaver=xscreensaver
+else
+    screensaver=`pgrep -l kscreensaver | grep -wc kscreensaver`
+    if [ $screensaver -ge 1 ]; then
+        screensaver=kscreensaver
+    else
+        screensaver=None
+        echo "No screensaver detected"
+    fi
+fi
+
+checkDelayProgs()
+{
+    for prog in "${delay_progs[@]}"; do
+        if [ `pgrep -lfc "$prog"` -ge 1 ]; then
+            notify-send "screensaver delayed because \"$prog\" is running..."
+            echo "Delaying the screensaver because a program on the delay list, \"$prog\", is running..."
+            delayScreensaver
+            break
+        fi
+    done
+}
+
+checkFullscreen()
+{
+    # loop through every display looking for a fullscreen window
+    for display in $displays
+    do
+        #get id of active window and clean output
+        activ_win_id=`DISPLAY=:0.${display} xprop -root _NET_ACTIVE_WINDOW`
+        #activ_win_id=${activ_win_id#*# } #gives error if xprop returns extra ", 0x0" (happens on some distros)
+        activ_win_id=${activ_win_id:40:9}
+
+        # Skip invalid window ids (commented as I could not reproduce a case
+        # where invalid id was returned, plus if id invalid
+        # isActivWinFullscreen will fail anyway.)
+        #if [ "$activ_win_id" = "0x0" ]; then
+        #     continue
+        #fi
+
+        # Check if Active Window (the foremost window) is in fullscreen state
+        isActivWinFullscreen=`DISPLAY=:0.${display} xprop -id $activ_win_id | grep _NET_WM_STATE_FULLSCREEN`
+            if [[ "$isActivWinFullscreen" = *NET_WM_STATE_FULLSCREEN* ]];then
+                isAppRunning
+                var=$?
+                if [[ $var -eq 1 ]];then
+                    delayScreensaver
+                fi
+            fi
+    done
+}
+
+
+
+
+
+# check if active windows is mplayer, vlc or firefox
+#TODO only window name in the variable activ_win_id, not whole line.
+#Then change IFs to detect more specifically the apps "<vlc>" and if process name exist
+
+isAppRunning()
+{
+    #Get title of active window
+    activ_win_title=`xprop -id $activ_win_id | grep "WM_CLASS(STRING)"`   # I used WM_NAME(STRING) before, WM_CLASS more accurate.
+
+
+
+    # Check if user want to detect Video fullscreen on Firefox, modify variable firefox_flash_detection if you dont want Firefox detection
+    if [ $firefox_flash_detection == 1 ];then
+        if [[ "$activ_win_title" = *unknown* || "$activ_win_title" = *plugin-container* ]];then
+        # Check if plugin-container process is running
+            flash_process=`pgrep -l plugin-containe | grep -wc plugin-containe`
+            #(why was I using this line avobe? delete if pgrep -lc works ok)
+            #flash_process=`pgrep -lc plugin-containe`
+            if [[ $flash_process -ge 1 ]];then
+                return 1
+            fi
+        fi
+    fi
+
+
+    # Check if user want to detect Video fullscreen on Chromium, modify variable chromium_flash_detection if you dont want Chromium detection
+    if [ $chromium_flash_detection == 1 ];then
+        if [[ "$activ_win_title" = *exe* ]];then
+        # Check if Chromium/Chrome Flash process is running
+            flash_process=`pgrep -lfc ".*((c|C)hrome|chromium).*flashp.*"`
+            if [[ $flash_process -ge 1 ]];then
+                return 1
+            fi
+        fi
+    fi
+
+
+    #check if user want to detect mplayer fullscreen, modify variable mplayer_detection
+    if [ $mplayer_detection == 1 ];then
+        if [[ "$activ_win_title" = *mplayer* || "$activ_win_title" = *MPlayer* ]];then
+            #check if mplayer is running.
+            #mplayer_process=`pgrep -l mplayer | grep -wc mplayer`
+            mplayer_process=`pgrep -lc mplayer`
+            if [ $mplayer_process -ge 1 ]; then
+                return 1
+            fi
+        fi
+    fi
+
+
+    # Check if user want to detect vlc fullscreen, modify variable vlc_detection
+    if [ $vlc_detection == 1 ];then
+        if [[ "$activ_win_title" = *vlc* ]];then
+            #check if vlc is running.
+            #vlc_process=`pgrep -l vlc | grep -wc vlc`
+            vlc_process=`pgrep -lc vlc`
+            if [ $vlc_process -ge 1 ]; then
+                return 1
+            fi
+        fi
+    fi
+
+    # Check if user want to detect minitube fullscreen, modify variable minitube_detection
+    if [ $minitube_detection == 1 ];then
+        if [[ "$activ_win_title" = *minitube* ]];then
+            #check if minitube is running.
+            #minitube_process=`pgrep -l minitube | grep -wc minitube`
+            minitube_process=`pgrep -lc minitube`
+            if [ $minitube_process -ge 1 ]; then
+                return 1
+            fi
+        fi
+    fi
+
+return 0
+}
+
+
+delayScreensaver()
+{
+
+    # reset inactivity time counter so screensaver is not started
+    if [ "$screensaver" == "xscreensaver" ]; then
+        xscreensaver-command -deactivate > /dev/null
+    elif [ "$screensaver" == "kscreensaver" ]; then
+        qdbus org.freedesktop.ScreenSaver /ScreenSaver SimulateUserActivity > /dev/null
+    fi
+
+
+    #Check if DPMS is on. If it is, deactivate and reactivate again. If it is not, do nothing.
+    dpmsStatus=`xset -q | grep -ce 'DPMS is Enabled'`
+    if [ $dpmsStatus == 1 ];then
+            xset -dpms
+            xset dpms
+    fi
+
+}
+
+
+
+delay=$1
+
+
+# If argument empty, use 50 seconds as default.
+if [ -z "$1" ];then
+    delay=50
+fi
+
+
+# If argument is not integer quit.
+if [[ $1 = *[^0-9]* ]]; then
+    echo "The Argument \"$1\" is not valid, not an integer"
+    echo "Please use the time in seconds you want the checks to repeat."
+    echo "You want it to be ~10 seconds less than the time it takes your screensaver or DPMS to activate"
+    exit 1
+fi
+
+
+while true
+do
+    checkDelayProgs
+    checkFullscreen
+    sleep $delay
+done
+
+
+exit 0
diff --git a/home-bin/maiumin b/home-bin/maiumin
new file mode 100644 (file)
index 0000000..3ff920d
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+if [ $# -lt 1 ]; then
+   echo " Uso: $0 percorso/nome-directory"
+   exit 1
+fi
+DIRIN="$PWD"
+DIRTOLOWER="$1"
+cd $DIRTOLOWER
+for MAIU in *
+  do
+     MINU=`echo $MAIU | tr 'A-Z' 'a-z'`
+     if [ "$MAIU" != "$MINU" ]; then
+        mv "$MAIU" "$MINU"
+     fi
+  done
+cd $DIRIN
+
diff --git a/home-bin/mkpass b/home-bin/mkpass
new file mode 100644 (file)
index 0000000..b687d1f
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/bash
+#  Su macchine un po' vecchie, 
+#+ potrebbe essere necessario cambiare l'intestazione in #!/bin/bash2.
+#
+#  Generatore di password casuali per Bash 2.x +
+#+ di Antek Sawicki <tenox@tenox.tc>,
+#+ che ha generosamente permesso all'autore de Guida ASB il suo utilizzo.
+#
+# ==> Commenti aggiunti dall'autore del libro ==>
+
+
+MATRICE='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?!.,-_$()^%&+"*'
+#MATRICE="abcdefghijklmnopqrstuvwxyz"
+#MATRICE="0123456789"
+#MATRICE="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+# ==> La password viene formata con caratteri alfanumerici.
+if [[ -z "$1" ]];then LUNGHEZZA=15;else LUNGHEZZA="$1";fi
+
+
+while [ "${n:=1}" -le "$LUNGHEZZA" ]
+# ==> Ricordo che := è l'operatore "sostiruzione default".
+# ==> Quindi, se 'n' non è stata inizializzata, viene impostata ad 1.
+do
+       PASS="$PASS${MATRICE:$(($RANDOM%${#MATRICE})):1}"
+       # ==> Molto intelligente e scaltro.
+
+       # ==> Iniziando dall'annidamento più interno...
+       # ==> ${#MATRICE} restituisce la lunghezza dell'array MATRICE.
+
+       # ==> $RANDOM%${#MATRICE} restituisce un numero casuale compreso tra 1
+       # ==> e [lunghezza di MATRICE] - 1.
+
+       # ==> ${MATRICE:$(($RANDOM%${#MATRICE})):1}
+       # ==> restituisce l'espansione di lunghezza 1 di MATRICE 
+       # ==> partendo da una posizione casuale.
+       # ==> Vedi la sostituzione di parametro {var:pos:lun},
+       # ==> con relativi esempi, al Capitolo 9.
+
+       # ==> PASS=... aggiunge semplicemente il risultato al precedente 
+       # ==> valore di PASS (concatenamento).
+
+       # ==> Per visualizzare tutto questo più chiaramente, 
+       # ==> decommentate la riga seguente
+       #                 echo "$PASS"
+       # ==> e vedrete come viene costruita PASS,
+       # ==> un carattere alla volta ad ogni iterazione del ciclo.
+
+       let n+=1
+       # ==> Incrementa 'n' per il passaggio successivo.
+done
+
+echo "$PASS"      # ==> Oppure, se preferite, redirigetela in un file.
+
+exit 0
diff --git a/home-bin/mvb b/home-bin/mvb
new file mode 100644 (file)
index 0000000..e2e86ec
--- /dev/null
@@ -0,0 +1,164 @@
+#! /bin/bash
+#----------------------------------------------------
+# Change the path above to point to the location on
+# your computer of either the Bourne shell (sh) or
+# the BASH (Bourne Again) shell (bash).
+#
+# This shell script was written to "batch rename" files
+# (change the name of many files at once) in the
+# current working directory.
+# 
+# For his personal use the author named this script
+# mvb (MV-Batch) in reference to the mv command
+# of *nix/Linux (which this script uses).
+#
+# Written by: Steve Doonan, Portales, NM US
+# Email: xscd at xscd.com
+# Date: October, 2003
+#----------------------------------------------------
+
+if [ $# -eq 0 ]
+   then
+      cat << _EOF_
+
+--------------------------------------------------
+You did not specify a NEWNAME for the files.
+
+After the name of the command please enter
+a SPACE followed by the name you would like
+all the files in the current directory to be
+renamed to.
+--------------------------------------------------
+
+_EOF_
+      exit
+fi
+
+NEWNAME="$(echo "$1" | tr -Cs '[:alnum:]' '_')"
+
+cat << _EOF_
+
+-------------------------------------------------------
+Rename files to--> $NEWNAME
+Current directory--> $(pwd)
+
+   Continue? (Press RETURN or ENTER)
+   TO QUIT, type q (then press RETURN or ENTER)
+   FOR INFORMATION, type i (then press RETURN or ENTER)
+-------------------------------------------------------
+
+_EOF_
+
+read CONTINUE
+case $CONTINUE
+in
+    [!i]* ) exit ;;
+       i  ) cat << _EOF_
+
+----------------------------------------------------------------
+INFORMATION
+
+This shell script (Bourne or BASH) will RENAME all visible files
+(files that don't begin with a dot) in the current directory, to
+a name you specify, appending a numerical index to each filename
+so that each is unique, and retaining the original filename
+extension, if one exists.
+
+This script will NOT rename subdirectories or symbolic links
+contained within the current directory, nor will it descend into
+subdirectories to rename files within them.
+
+If the script does not see what looks like an existing valid
+FILENAME EXTENSION (3-4 characters following a dot at the end
+of a filename), it will ask for one. If you WANT to add a
+filename extension, just type 3 or 4 characters (i.e. jpg, txt,
+html), with or without a preceding dot--the script will provide
+the dot if you do not. If you do NOT want the filename to have
+an extension, just press RETURN or ENTER at that prompt without
+typing any characters, and no filename extension will be added.
+
+To QUIT this program at any time, press CONTROL-C
+To CONTINUE, press RETURN or ENTER
+----------------------------------------------------------------
+
+_EOF_
+    read CONTINUE ;;
+esac
+
+if [ -z $2 ];then
+       INDEX=0
+else
+       INDEX=$2
+fi
+
+make_zero-padded_index_number ()
+{
+INDEX=$(($INDEX + 1))
+INDEX_COUNT="$(echo "$INDEX" | wc -c)"
+PADDING_ZEROS="$(ls "$(pwd)" | wc -l | tr '[:digit:]' '0' | tr -d '[:space:]')"
+INDEX_ALPHANUMERIC="$(echo "${PADDING_ZEROS}${INDEX}" | cut -c$INDEX_COUNT-)"
+}
+
+for I in *
+   do
+      #-----------------------------------------
+      # if file is NOT a directory or a link...
+      #-----------------------------------------
+      if [ -f "$I" -a ! -L "$I" ]
+         then
+         #-----------------------------------------------
+         # if filename has a 3 or 4 character extension...
+         #-----------------------------------------------
+         if echo "$I" | grep "\.[^.0-9]\{3,4\}$" > /dev/null
+            then
+            #------------------------------------------------
+            # assign filename extension to variable EXTENSION
+            #------------------------------------------------
+            EXTENSION="$(echo "$I" | sed 's/^.*\(\.[^.]*\)$/\1/')"
+         #-----------------------------------------------------
+         # otherwise, ask for a filename extension (or none)...
+         #-----------------------------------------------------
+         else
+            echo ""
+            echo '------------------------------------------------------------'
+            echo "FILENAME: $I"
+            echo "No (or improbable) filename extension found"
+            echo "Enter new filename extension, or press RETURN or ENTER"
+            echo -n "for no filename extension: "
+            read NEW_EXTENSION
+            #-----------------------------------------------------------------
+            # cut the new extension (if any) down to no more than 4 characters
+            #-----------------------------------------------------------------
+            NEW_EXTENSION="$(echo "$NEW_EXTENSION" | sed 's/^\.*\(.\{0,4\}\).*$/\1/'| tr -C '[:alnum:]\12' '_' )"
+            echo '------------------------------------------------------------'
+            echo ""
+            if [ -n "$NEW_EXTENSION" ]
+               then
+               EXTENSION=".${NEW_EXTENSION}"
+            else
+               EXTENSION=''
+            fi
+         fi
+         #----------------------------------------------------------------------
+         # at this point, EXTENSION should be set correctly--an alphanumeric
+         # index number is created (by the function make_zero-padded_index_number)
+         # and the file is renamed (with a slightly different name if the computed
+         # filename already exists in the current directory).
+         #----------------------------------------------------------------------
+         make_zero-padded_index_number
+         RENAME_TO="${NEWNAME}${INDEX_ALPHANUMERIC}${EXTENSION}"
+         if [ -e "$RENAME_TO" ]
+            then
+               RENAME_TO="${NEWNAME}${INDEX_ALPHANUMERIC}a${EXTENSION}"
+         fi
+         chmod 664 "$I"
+         mv -iv -- "$I" "$RENAME_TO"
+      fi
+   done
+cat << _EOF_
+
+---------------------------------------------------
+  The files have been renamed. Script exiting...
+---------------------------------------------------
+
+_EOF_
diff --git a/home-bin/my_netstat b/home-bin/my_netstat
new file mode 100644 (file)
index 0000000..89071cb
--- /dev/null
@@ -0,0 +1,8 @@
+clear;
+while (( 1 ));
+    do
+      netstat --tcp --udp -nap | sed -n '1p;2{s/PID/  PID/;p};/ESTABLISHED/{s/D/D   /;p;d};/LISTEN/{s/N/N   /;p;d};/0\.0\.0\.0:/{s/\( \)\([[:digit:]]\+\/.*\)/\1   \2/;p;d};'
+      sleep 5;
+      clear;
+    done
+
diff --git a/home-bin/nopy.sh b/home-bin/nopy.sh
new file mode 100644 (file)
index 0000000..e61bfcf
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/bash
+echo -e "\n\e[38;5;146m      ,.....,\e[0m"
+echo -e "\e[38;5;146m   pd\"\"\"\`\`\`\"\"\"bq\e[0m"
+echo -e "\e[38;5;146m  6P           YA\e[0m"
+echo -e "\e[38;5;146m 6M'  \e[0mdMpMMMo.\e[38;5;146m  \`Mb\e[0m  ,pW\"Wq.  dMMMMAo. dM    MF"
+echo -e "\e[38;5;146m MN   \e[0mMM    MM\e[38;5;146m   8M\e[0m 6W'   \`Wb MM   \`Wb  VA  ,V"
+echo -e "\e[38;5;146m YM.  \e[0mMM    MM\e[38;5;146m  ,M9\e[0m YA.   ,A9 MM   ,AP   VVV\""
+echo -e "\e[38;5;146m  Mb  \e[0mJM    MM\e[38;5;146m  dM\e[0m   \`Ybmd9'  MMbmmd'    ,V"
+echo -e "\e[38;5;146m   Ybq,______,pdY\e[0m             MM        ,V"
+echo -e "\e[38;5;146m     \"\"\"\`\`\`\`\"\"\"\e[0m               JM       Ob\" \e[38;5;146mv\e[0m0.1"
+echo -e "\e[0m"
+
+# Make sure we have the required binaries
+has_curl=`which curl 2>/dev/null || echo false`
+has_jq=`which jq 2>/dev/null || echo false`
+has_aria2=`which aria2c 2>/dev/null || echo false`
+
+if [ $has_curl = "false" ]; then
+       echo -e " ! Error: Missing curl binary\n"
+       exit 1
+fi
+
+if [ $has_jq = "false" ]; then
+       echo -e " ! Error: Missing jq binary\n"
+       exit 1
+fi
+
+# Make sure the supplied URL looks valid
+if [ "$(grep -Poc 'nopy.to\/([a-zA-Z0-9]{8})\/(.*?)$' <<< $1)" -lt 1 ]; then
+       echo -e " ! Error: Only nopy.to URLs are supported\n"
+       exit 1
+fi
+
+# Parse the code and file from URL
+code=`echo $1 | sed -n 's/.*nopy.to\/\([a-zA-Z0-9]\{8\}\)\/.*/\1/p'`
+file=`echo $1 | sed -n 's/.*nopy.to\/[a-zA-Z0-9]\{8\}\/\(.*\)\/\?/\1/p'`
+
+# Fetch the session
+echo -e " Fetching session ...\n"
+sessionreq=`curl -s --data-urlencode "code=$code" --data-urlencode "file=$file" -X POST https://data.nopy.to/file`
+
+if [ "$(echo $sessionreq | jq -r '.status')" != "ok" ]; then
+       echo -e " ! Error: Session request failed\n"
+       exit 1
+fi
+
+if [ "$(echo $sessionreq | jq -r '.msg.error_fatal')" != "false" ]; then
+       echo -e " ! Error: Nopy is having technical issues\n"
+       exit 1
+fi
+
+if [ -f "$(echo $sessionreq | jq -r '.msg.filename')" ]; then
+       echo -e " ! Error: File \"$(echo $sessionreq | jq -r '.msg.filename')\" already exists\n"
+       exit 1
+fi
+
+# Fetch the download URL
+echo -e " Requesting download ticket ...\n"
+downloadreq=`curl -s --data-urlencode "code=$code" --data-urlencode "fid=$(echo $sessionreq | jq -r '.msg.fid')" --data-urlencode "request=$(echo $sessionreq | jq -r '.msg.request')" --data-urlencode "session=$(echo $sessionreq | jq -r '.msg.session')" -X POST https://data.nopy.to/download`
+
+if [ "$(echo $downloadreq | jq -r '.status')" != "ok" ]; then
+       echo -e " ! Error: Download request failed\n"
+       exit 1
+fi
+
+# Finally, download the file
+echo -e " Downloading \"$file\" from: $(echo $downloadreq | jq -r '.msg.server') ..."
+
+if [ $has_aria2 = "false" ]; then
+       echo -e ""
+       curl -J -O "$(echo $downloadreq | jq -r '.msg.download')"
+else
+       aria2c -x 4 --summary-interval=0 --auto-file-renaming=false "$(echo $downloadreq | jq -r '.msg.download')"
+fi
+
+echo -e "\n Finished!\n"
+
diff --git a/home-bin/nospaces b/home-bin/nospaces
new file mode 100644 (file)
index 0000000..905652d
--- /dev/null
@@ -0,0 +1,14 @@
+#! /bin/bash
+
+dir_to_work=$1
+IFS=$'\n\t'
+
+cd $dir_to_work
+
+for item in *; do
+       item_nospace=$(echo $item |tr [:blank:] '_')
+       if [[ $item != $item_nospace ]];then
+               mv $item $item_nospace
+       fi
+done
+
diff --git a/home-bin/notes b/home-bin/notes
new file mode 100644 (file)
index 0000000..a9afd60
--- /dev/null
@@ -0,0 +1,726 @@
+#! /bin/bash
+
+# bash-notes © 2023 by danix is licensed under CC BY-NC 4.0. 
+# To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
+
+# to debug the script run it like:
+# DEBUG=true notes.sh ...
+# and check /tmp/debug_bash-notes.log
+if [[ $DEBUG == true ]]; then
+       exec 5> /tmp/debug_bash-notes.log
+       BASH_XTRACEFD="5"
+       PS4='$LINENO: '
+       set -x
+fi
+
+PID=$$
+BASENAME=$( basename "$0" )
+NOW=$(date +%s)
+
+VERSION="0.4git"
+DBVERSION=${VERSION}_${NOW}
+
+set_defaults() {
+# Binaries to use
+JQ=${JQ:-/usr/bin/jq}
+EDITOR=${EDITOR:-/usr/bin/vim}
+TERMINAL=${TERMINAL:-/usr/bin/alacritty}
+# Git binary only used if $USEGIT is true - See below
+GIT=${GIT:-/usr/bin/git}
+# add options for your terminal. Remember to add the last option to execute
+# your editor program, otherwise the script will fail.
+# see example in the addnote function
+TERM_OPTS="--class notes --title notes -e "
+# Setting PAGER here overrides whatever is set in your default shell
+# comment this option to use your default pager if set in your shell.
+PAGER=${PAGER:-/usr/bin/more}
+
+# set this to true to have output in plain text
+# or use the -p option on the command line before every other option
+PLAIN=false
+# base directory for program files
+BASEDIR=${BASEDIR:-~/.local/share/bash-notes}
+# notes database in json format
+DB=${BASEDIR}/db.json
+# directory containing the actual notes
+NOTESDIR=${BASEDIR}/notes
+
+### GIT SUPPORT
+
+# If you want to store your notes in a git repository set this to true
+USEGIT=true
+# Address of your remote repository. Without this GIT will refuse to work
+GITREMOTE=${GITREMOTE:-""}
+# How long should we wait (in seconds) between sync on the git remote. Default 3600 (1 hour)
+GITSYNCDELAY=${GITSYNCDELAY:-"3600"}
+# The name of this client. If left empty, defaults to the output of hostname
+GITCLIENT=${GITCLIENT:-""}
+
+} # end set_defaults, do not change this line.
+
+set_defaults
+
+# Do not edit below this point
+RCFILE=${RCFILE:-~/.config/bash-notes.rc}
+TMPDB=/tmp/db.json
+
+if [ ! -x "$JQ" ]; then
+       echo "jq not found in your PATH"
+       echo "install jq to continue"
+       exit 1
+fi
+
+# IMPORT USER DEFINED OPTIONS IF ANY
+if [[ -f $RCFILE ]]; then
+       # shellcheck disable=SC1090
+       source "$RCFILE"
+fi
+
+# We prevent the program from running more than one instance:
+PIDFILE=/var/tmp/$(basename "$0" .sh).pid
+
+# Make sure the PID file is removed when we kill the process
+trap 'rm -f $PIDFILE; exit 1' TERM INT
+
+if [[ -r $PIDFILE ]]; then
+       # PIDFILE exists, so I guess there's already an instance running
+       # let's kill it and run again
+       # shellcheck disable=SC2046,SC2086
+       kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
+       # should already be deleted by trap, but just to be sure
+       rm "$PIDFILE"
+fi
+
+# create PIDFILE
+echo $PID > "$PIDFILE"
+
+# Export config to file
+export_config() {
+       if [ -r ${RCFILE} ]; then
+               echo "Backing up current '${RCFILE}'...."
+               mv -f ${RCFILE} ${RCFILE}.$(date +%Y%m%d_%H%M)
+       fi
+       echo "Writing '${RCFILE}'...."
+       sed  -n '/^set_defaults() {/,/^} # end set_defaults, do not change this line./p' $0 \
+       | grep -v set_defaults \
+       | sed -e 's/^\([^=]*\)=\${\1:-\([^}]*\)}/\1=\2/' \
+       > ${RCFILE}
+       if [ -r ${RCFILE} ]; then
+               echo "Taking no further action."
+               exit 0
+       else
+               echo "Could not write '${RCFILE}'...!"
+               exit 1
+       fi
+}
+
+# we should expand on this function to add a sample note and explain a little bit
+# how the program works.
+firstrun() {
+       [ -f $RCFILE ] && RC=$RCFILE || RC="none"
+
+       clear
+       echo "${BASENAME} configuration:
+
+base directory:                ${BASEDIR}/
+notes archive:         ${NOTESDIR}/
+notes database:                ${DB}
+rc file:               $RC
+text editor:           ${EDITOR}
+terminal:              ${TERMINAL}
+jq executable:         ${JQ}
+"
+
+       echo "Now I'll create the needed files and directories."
+       read -r -p "Do you wish to continue? (y/N) " ANSWER
+       case $ANSWER in
+               y|Y )
+                       mkdir -p $NOTESDIR
+                       cat << __EOL__ > ${DB}
+{
+       "params": {
+               "version": "${VERSION}",
+               "dbversion": "${DBVERSION}"
+       },
+       "git": {
+               "lastpull": ""
+       },
+       "notes": []
+}
+__EOL__
+                       echo; echo "All done, you can now write your first note."
+                       ;;
+               * )
+                       echo "No changes made. Exiting"
+                       exit
+                       ;;
+       esac
+}
+
+# check for notes dir existance and create it in case it doesn't exists
+if [[ ! -d $NOTESDIR ]]; then
+       # we don't have a directory. FIRST RUN?
+       firstrun
+fi
+# check if input is a number, returns false or the number itself
+check_noteID() {
+       IN=$1
+       case $IN in
+               ''|*[!0-9]*)
+                       false
+                       ;;
+               *)
+                       echo "$IN"
+                       ;;
+       esac
+}
+
+helptext() {
+    echo "Usage:"
+    echo "  $0 [PARAMS] [note ID]..."
+       echo ""
+    echo "${BASENAME} parameters are:"
+    echo -e "  -h | --help\t\t\t: This help text"
+    echo -e "  -p | --plain\t\t\t: Output is in plain text"
+    echo -e "\t\t\t\t  (without this option the output is formatted)"
+    echo -e "\t\t\t\t  (this option must precede all others)"
+    echo -e "  -l | --list\t\t\t: List existing notes"
+    echo -e "  -a | --add=[\"<title>\"]\t: Add new note"
+    echo -e "  -e | --edit=[<note>]\t\t: Edit note"
+    echo -e "  -d | --delete=[<note> | all]    : Delete single note or all notes at once"
+    echo -e "  -s | --show=[<note>]\t\t: Display note using your favourite PAGER"
+    echo -e "  -r | --restore=[<dir>]\t: Restore a previous backup from dir"
+    echo -e "  -v | --version\t\t: Print version"
+    echo -e "  --userconf\t\t\t: Export User config file"
+    echo -e "  --backup [<dest>]\t\t: Backup your data in your destination folder"
+    echo -e "  --showconf\t\t\t: Display running options"
+    echo -e "  --sync\t\t\t: Sync notes to git repository"
+    echo ""
+    echo -e "if a non option is passed and is a valid note ID, the note will be displayed."
+}
+
+configtext() {
+    [ $USEGIT ] && GITUSE="enabled" || GITUSE="disabled"
+    if [ -n $GITCLIENT ]; then
+        CLIENTGIT="$( hostname )"
+    else
+        CLIENTGIT="$GITCLIENT"
+    fi
+    clear
+    echo -e "${BASENAME} configuration is:"
+
+    echo -e "\tbase directory:     ${BASEDIR}/"
+    echo -e "\tnotes archive:      ${NOTESDIR}/"
+    echo -e "\tnotes database:     ${DB}"
+    echo -e "\trc file:            $RCFILE"
+    echo -e "\tdebug file:         /tmp/debug_bash-note.log"
+    echo
+    echo -e "\ttext editor:        ${EDITOR}"
+    echo -e "\tterminal:           ${TERMINAL}"
+    echo -e "\tjq executable:      ${JQ}"
+    echo -e "\tPAGER:              ${PAGER}"
+    echo
+    echo -e "\tGIT:                ${GITUSE} - ${GIT}"
+    echo -e "\tGIT remote:         ${GITREMOTE}"
+    echo -e "\tGIT sync delay:     ${GITSYNCDELAY}"
+    echo -e "\tGIT client name:    ${CLIENTGIT}"
+}
+
+# this function returns a random 2 words title
+random_title() {
+    # Constants 
+    X=0
+    DICT=/usr/share/dict/words
+    OUTPUT=""
+     
+    # total number of non-random words available 
+    COUNT=$(cat $DICT | wc -l)
+     
+    # while loop to generate random words  
+    while [ "$X" -lt 2 ] 
+    do 
+        RAND=$(od -N3 -An -i /dev/urandom | awk -v f=0 -v r="$COUNT" '{printf "%i\n", f + r * $1 / 16777216}')
+        OUTPUT+="$(sed `echo $RAND`"q;d" $DICT)"
+        (("X = X + 1"))
+        [[ $X -eq 1 ]] && OUTPUT+=" "
+    done
+
+    echo $OUTPUT
+}
+
+# check if GITCLIENT has been set or set it to the output of hostname
+if [ -z "$GITCLIENT" ]; then
+    GITCLIENT=$( hostname )
+fi
+# returns true if the argument provided directory is a git repository
+is_git_repo() {
+    DIR=$1
+    if [[ -d $DIR ]]; then
+        cd $DIR
+        if git rev-parse 2>/dev/null; then
+            true
+        else
+            false
+        fi
+    fi
+}
+
+# sync local repository to remote
+# accepts -f parameter to skip last sync check
+gitsync() {
+    FORCE=$1
+    if [[ $USEGIT && -n $GITREMOTE ]]; then
+        [ $PLAIN == false ] && echo "Syncing notes with git on remote \"$GITREMOTE\""
+        NOWSYNC=$(date +%s)
+        if [[ $FORCE == "-f" ]]; then
+            $JQ --arg n "$NOWSYNC" '.git["lastpull"] = $n' "$DB" > $TMPDB
+            mv $TMPDB $DB
+            cd $BASEDIR
+            [ $PLAIN == false ] && $GIT pull || $GIT pull -q
+        else
+            # LASTSYNC is the last time we synced to the remote, or 0 if it's the first time.
+            LASTSYNC=$($JQ -r '.git["lastpull"] // 0' "$DB")
+            SYNCDIFF=$(( ${NOWSYNC} - ${LASTSYNC} ))
+            if (( $SYNCDIFF > $GITSYNCDELAY )); then
+                #more than our delay time has passed. We can sync again.
+                $JQ --arg n "$NOWSYNC" '.git["lastpull"] = $n' "$DB" > $TMPDB
+                mv $TMPDB $DB
+                cd $BASEDIR
+                [ $PLAIN == false ] && $GIT pull || $GIT pull -q
+            else
+                # Last synced less than $GITSYNCDELAY seconds ago. We shall wait
+                [ $PLAIN == false ] && echo "Last synced less than $GITSYNCDELAY seconds ago. We shall wait"
+            fi
+        fi
+    else
+        # no git, so we just keep going
+        true
+    fi
+}
+
+# add note to git and push it to remote
+gitadd() {
+    if [[ $USEGIT && -n $GITREMOTE ]]; then
+        [ $PLAIN == false ] && echo "Adding note to remote \"$GITREMOTE\""
+        cd $BASEDIR
+        $GIT add .
+        $GIT commit -m "$(basename $0) - adding note from ${GITCLIENT}"
+        $GIT push origin master
+    else
+        # no git, so we just keep going
+        true
+    fi
+}
+
+# edited note added to git and pushed it to remote
+gitedit() {
+    if [[ $USEGIT && -n $GITREMOTE ]]; then
+        [ $PLAIN == false ] && echo "Editing note on remote \"$GITREMOTE\""
+        cd $BASEDIR
+        $GIT add .
+        $GIT commit -m "$(basename $0) - ${GITCLIENT} note edited."
+        $GIT push origin master
+    else
+        # no git, so we just keep going
+        true
+    fi
+}
+
+# add note to git and push it to remote
+gitremove() {
+    NOTE=$1
+    FILE=$2
+    if [[ $USEGIT && -n $GITREMOTE ]]; then
+        [ $PLAIN == false ] && echo "Deleting notes from remote \"$GITREMOTE\""
+        if [ "all" == $NOTE ];then
+            echo "Deleting all notes"
+            cd $BASEDIR
+            $GIT rm notes/*
+            $GIT commit -m "$(basename $0) - ${GITCLIENT} removing all notes."
+            $GIT push origin master
+        else
+            local OK=$(check_noteID "$NOTE")
+            if [[ "$OK" ]]; then
+                echo "Deleting note ID ${NOTE}"
+                cd $BASEDIR
+                $GIT rm notes/${FILE}
+                $GIT add .
+                $GIT commit -m "$(basename $0) - ${GITCLIENT} removing note ID ${NOTE}."
+                $GIT push origin master
+            fi
+        fi
+    else
+        # no git, so we just keep going
+        true
+    fi
+}
+
+# check for USEGIT and subsequent variables
+if [[ $USEGIT && -n $GITREMOTE ]]; then
+    # GIT is a go.
+    if ! is_git_repo $BASEDIR; then
+        # initializing git repository
+        cd $BASEDIR
+        $GIT init
+        echo "adding all files to git"
+        $GIT add .
+        $GIT commit -m "$(basename $0) - initial commit from ${GITCLIENT}"
+        $GIT remote add origin $GITREMOTE
+        $GIT push -u origin master
+    fi
+elif [[ $USEGIT && -z $GITREMOTE ]]; then
+    echo "GITREMOTE variable not set. reverting USEGIT to false"
+    USEGIT=false
+fi
+
+addnote() {
+       # attempt syncing before adding a note
+       gitsync -f
+       # remove eventually existing temp DB file
+       if [[ -f $TMPDB ]]; then
+               rm $TMPDB
+       fi
+
+       # RANDOM TITLE
+       RTITLE=$(random_title)
+
+       if [[ -z $1 ]]; then
+               read -r -p "Title: " TITLE
+               case "$TITLE" in
+                       '' )
+                               NOTETITLE="$RTITLE"
+                               ;;
+                       * )
+                               NOTETITLE=$TITLE
+                               ;;
+               esac
+       fi
+
+       # [[ -z "$1" ]] && NOTETITLE="$RTITLE" || NOTETITLE="$1"
+       echo "adding new note - \"$NOTETITLE\""
+       # shellcheck disable=SC2086
+       LASTID=$($JQ '.notes[-1].id // 0 | tonumber' $DB)
+       # [ "" == $LASTID ] && LASTID=0
+       NOTEID=$(( LASTID + 1 ))
+       # shellcheck disable=SC2086
+       touch ${NOTESDIR}/${NOW}
+       # shellcheck disable=SC2016
+       $JQ --arg i "$NOTEID" --arg t "$NOTETITLE" --arg f "$NOW" '.notes += [{"id": $i, "title": $t, "file": $f}]' "$DB" > $TMPDB
+       # shellcheck disable=SC2086
+       mv $TMPDB $DB
+       # example for alacritty:
+       # alacritty --class notes --title notes -e /usr/bin/vim ...
+       # shellcheck disable=SC2086,SC2091
+       $(${TERMINAL} ${TERM_OPTS} ${EDITOR} ${NOTESDIR}/${NOW})
+       # add note to git and push to remote
+       gitadd
+}
+backup_data() {
+       BACKUPDIR="$1"
+    echo "backing up data in $BACKUPDIR"
+
+
+    if [ -d $BACKUPDIR ]; then
+       if [ $(/bin/ls -A $BACKUPDIR) ]; then
+               echo "$BACKUPDIR is not empty. Cannot continue"
+               exit
+           else
+               echo "$BACKUPDIR is ok. Continuing!"
+           fi
+       else
+               # BACKUPDIR doesn't exists
+               echo "$BACKUPDIR doesn't exists"
+               read -r -p "Do you want me to create it for you? (y/N) " ANSWER
+               case $ANSWER in
+                       y|Y )
+                               mkdir -p $BACKUPDIR
+                               ;;
+                       * )
+                               echo "No changes made. Exiting"
+                               exit
+                               ;;
+               esac
+    fi
+    # ok, we have a backup directory
+    if [ -r $RCFILE ]; then
+       BCKUP_COMM=$(rsync -avz --progress ${RCFILE}* ${BASEDIR}/ ${BACKUPDIR})
+    else
+       BCKUP_COMM=$(rsync -avz --progress ${BASEDIR}/ ${BACKUPDIR})
+    fi
+    # run the command
+    if [ "$BCKUP_COMM" ]; then 
+           echo -e "All files backed up."
+           echo -e "BACKUP directory:\t$BACKUPDIR"
+           tree $BACKUPDIR | $PAGER
+           echo; echo "BACKUP COMPLETED"
+       fi
+}
+
+backup_restore() {
+       BACKUPDIR="$1"
+       echo "restoring backup from $BACKUPDIR"
+       echo "This will overwrite all your notes and configurations with the backup."
+       read -r -p "Do you want to continue? (y/N) " ANSWER
+       case $ANSWER in
+               y|Y )
+                       # restoring rc file
+                       BACKUPRC=$(basename $RCFILE)
+                       if [ -r ${BACKUPDIR}/${BACKUPRC} ]; then
+                               if [ -r ${RCFILE} ]; then
+                                       echo "Backing up current '${RCFILE}'...."
+                                       mv -f ${RCFILE} ${RCFILE}.$(date +%Y%m%d_%H%M)
+                               fi
+                               cp --verbose ${BACKUPDIR}/${BACKUPRC} $RCFILE
+                       fi
+                       # restoring notes directory
+                       if [ -d $BACKUPDIR/notes ]; then
+                               if [ $(/bin/ls -A $NOTESDIR) ]; then
+                                       rm --verbose $NOTESDIR/*
+                               fi
+                               cp -r --verbose $BACKUPDIR/notes $BASEDIR
+                       fi
+                       # restoring database
+                       BACKUPDB=$(basename $DB)
+                       if [ -f ${BACKUPDIR}/${BACKUPDB} ]; then
+                               if [ -r ${DB} ]; then
+                                       echo "Backing up current '${DB}'...."
+                                       mv -f ${DB} ${DB}.$(date +%Y%m%d_%H%M)
+                               fi
+                               cp --verbose ${BACKUPDIR}/${BACKUPDB} $DB
+                       fi
+                       # restoring git repo subdirectory
+                       if [ -d $BACKUPDIR/.git ]; then
+                               if [ /bin/ls -A ${BASEDIR}/.git ]; then
+                                       rm -rf ${BASEDIR}/.git
+                               fi
+                               cp -r --verbose ${BACKUPDIR}/.git ${BASEDIR}/
+                       fi
+                       ;;
+               * )
+                       echo "No changes made. Exiting"
+                       exit
+                       ;;
+       esac
+}
+
+editnote() {
+       NOTE=$1
+       # shellcheck disable=SC2155
+       local OK=$(check_noteID "$NOTE")
+       if [ ! "$OK" ]; then
+               echo "invalid note \"$NOTE\""
+               echo "Use the note ID that you can fetch after listing your notes"
+               exit 1
+       fi
+
+       # shellcheck disable=SC2016,SC2086
+       TITLE=$($JQ --arg i $OK '.notes[] | select(.id == $i) | .title' $DB)
+       # shellcheck disable=SC2016,SC2086
+       FILE=$($JQ -r --arg i $OK '.notes[] | select(.id == $i) | .file' $DB)
+       if [ "$TITLE" ]; then
+               echo "editing note $TITLE"
+               # shellcheck disable=SC2086,SC2091
+               $(${TERMINAL} ${TERM_OPTS} ${EDITOR} ${NOTESDIR}/${FILE})
+               gitedit
+       else
+                echo "note not found"
+                exit 1
+       fi
+}
+listnotes() {
+       # attempt syncing before listing all notes
+       gitsync
+       # [ $PLAIN == true ] && echo "output is plain text" || echo "output is colored"
+       if [[ $(ls -A "$NOTESDIR") ]]; then
+               if [ $PLAIN == false ]; then
+                       echo "listing all notes"
+                       echo ""
+               fi
+               [ $PLAIN == false ] && echo "[ID]       [TITLE]         [CREATED]"
+               for i in "${NOTESDIR}"/*; do
+                       # shellcheck disable=SC2155
+                       local fname=$(basename $i)
+                       DATE=$(date -d @${fname} +"%d/%m/%Y %R %z%Z")
+                       # shellcheck disable=SC2016,SC2086
+                       TITLE=$($JQ -r --arg z $(basename $i) '.notes[] | select(.file == $z) | .title' $DB)
+                       # shellcheck disable=SC2016,SC2086
+                       ID=$($JQ -r --arg z $(basename $i) '.notes[] | select(.file == $z) | .id' $DB)
+                       [ $PLAIN == false ] && echo "[${ID}]    ${TITLE}        ${DATE}" || echo "${ID} - ${TITLE} - ${DATE}"
+               done
+       else
+               echo "no notes yet. You can add your first one with: ${BASENAME} -a \"your note title\""
+       fi
+}
+rmnote() {
+       # remove eventually existing temp DB file
+       if [[ -f $TMPDB ]]; then
+               rm $TMPDB
+       fi
+
+       NOTE=$1
+       if [ "all" == "$NOTE" ]; then
+               echo "You're going to delete all notes."
+               read -r -p "Do you wish to continue? (y/N) " ANSWER
+               case $ANSWER in
+                       y|Y )
+                               # shellcheck disable=SC2086
+                               $JQ 'del(.notes[])' $DB > $TMPDB
+                               # shellcheck disable=SC2086
+                               mv $TMPDB $DB
+                               # shellcheck disable=SC2086
+                               rm $NOTESDIR/*
+                               gitremove "all"
+                               echo "Deleted all notes"
+                               ;;
+                       * )
+                               echo "Aborting, no notes were deleted."
+                               exit 1
+                               ;;
+               esac
+       else
+               # shellcheck disable=SC2155
+               local OK=$(check_noteID "$NOTE")
+               if [ ! "$OK" ]; then
+                       echo "invalid note \"$NOTE\""
+                       echo "Use the note ID that you can fetch after listing your notes"
+                       sleep 1
+                       exit 1
+               fi
+
+               # shellcheck disable=SC2016,SC2086
+               TITLE=$($JQ --arg i $OK '.notes[] | select(.id == $i) | .title' $DB)
+               # shellcheck disable=SC2016,SC2086
+               FILE=$($JQ -r --arg i $OK '.notes[] | select(.id == $i) | .file' $DB)
+               if [ "$TITLE" ]; then
+                       # shellcheck disable=SC2016,SC2086
+                       $JQ -r --arg i $OK 'del(.notes[] | select(.id == $i))' $DB > $TMPDB
+                       # shellcheck disable=SC2086
+                       mv $TMPDB $DB
+                       rm $NOTESDIR/$FILE
+                       gitremove $OK $FILE
+                       echo "Deleted note $TITLE"
+                       sleep 1
+                       exit
+               else
+                        echo "note not found"
+                        sleep 1
+                        exit 1
+               fi
+       fi
+}
+shownote() {
+       NOTE=$1
+
+       # shellcheck disable=SC2155
+       local OK=$(check_noteID "$NOTE")
+       if [ ! "$OK" ]; then
+               echo "invalid note \"$NOTE\""
+               echo "Use the note ID that you can fetch after listing your notes"
+               exit 1
+       fi
+
+       FILE=$($JQ -r --arg i $OK '.notes[] | select(.id == $i) | .file' $DB)
+
+       if [ "$FILE" ]; then
+               $PAGER ${NOTESDIR}/${FILE}
+       fi
+}
+# shellcheck disable=SC2006
+GOPT=$(getopt -o hvplr:a::e:d:s: --long help,version,list,plain,userconf,showconf,sync,restore:,backup:,add::,edit:,delete:,show: -n 'bash-notes' -- "$@")
+
+# shellcheck disable=SC2181
+if [ $? != 0 ] ; then helptext >&2 ; exit 1 ; fi
+
+# Note the quotes around `$GOPT': they are essential!
+eval set -- "$GOPT"
+unset GOPT
+
+while true; do
+       case "$1" in
+               -h | --help )
+                       helptext
+               exit
+               ;;
+               -v | --version )
+                       echo $BASENAME v${VERSION}
+                       exit
+                       ;;
+           -p | --plain )
+                       PLAIN=true
+                       shift
+               ;;
+           -l | --list )
+                       listnotes
+                       exit
+               ;;
+           -a | --add )
+                       TITLE=$2
+                       shift 2
+                       addnote "$TITLE"
+                       exit
+               ;;
+               -e | --edit )
+                       NOTE=$2
+                       shift 2
+                       editnote "$NOTE"
+                       exit
+                       ;;
+               -d | --delete )
+                       NOTE=$2
+                       shift 2
+                       rmnote "$NOTE"
+                       exit
+                       ;;
+               -s | --show )
+                       NOTE=$2
+                       shift 2
+                       shownote "$NOTE"
+                       exit
+                       ;;
+               -r | --restore )
+                       RDIR=$2
+                       shift 2
+                       backup_restore $RDIR
+                       exit
+                       ;;
+               --sync )
+                       # I'm forcing it because if you run it manually, chances are that you need to.
+                       gitsync -f
+                       shift
+                       exit
+                       ;;
+               --userconf )
+                       export_config
+                       # shellcheck disable=SC2317
+                       echo "config exported to \"$RCFILE\""
+                       # shellcheck disable=SC2317
+                       exit
+                       ;;
+               --showconf )
+                       configtext
+                       exit
+                       ;;
+               --backup )
+                       BDIR=$2
+                       shift 2
+                       backup_data $BDIR
+                       exit
+                       ;;
+               -- )
+                       shift
+                       break
+                       ;;
+               * )
+                       break
+                       ;;
+       esac
+done
+
+for arg; do
+       if [ $(check_noteID $arg) ]; then
+               shownote $arg
+       else
+               helptext
+               exit
+       fi
+done
diff --git a/home-bin/polybar-kdeconnect.sh b/home-bin/polybar-kdeconnect.sh
new file mode 100644 (file)
index 0000000..9f576c1
--- /dev/null
@@ -0,0 +1,123 @@
+#!/usr/bin/env bash
+
+# CONFIGURATION
+LOCATION=0
+YOFFSET=0
+XOFFSET=0
+WIDTH=12
+WIDTH_WIDE=24
+THEME="darknix/main"
+
+# Color Settings of Icon shown in Polybar
+COLOR_DISCONNECTED='#000'       # Device Disconnected
+COLOR_NEWDEVICE='#ff0'          # New Device
+COLOR_BATTERY_90='#fff'         # Battery >= 90
+COLOR_BATTERY_80='#ccc'         # Battery >= 80
+COLOR_BATTERY_70='#aaa'         # Battery >= 70
+COLOR_BATTERY_60='#888'         # Battery >= 60
+COLOR_BATTERY_50='#666'         # Battery >= 50
+COLOR_BATTERY_LOW='#f00'        # Battery <  50
+
+# Icons shown in Polybar
+ICON_SMARTPHONE=''
+ICON_TABLET=''
+SEPERATOR='|'
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+show_devices (){
+    IFS=$','
+    devices=""
+    for device in $(qdbus --literal org.kde.kdeconnect /modules/kdeconnect org.kde.kdeconnect.daemon.devices); do
+        deviceid=$(echo "$device" | awk -F'["|"]' '{print $2}')
+        devicename="$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid" org.kde.kdeconnect.device.name)"
+        devicetype=$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid" org.kde.kdeconnect.device.type)
+        isreach="$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid" org.kde.kdeconnect.device.isReachable)"
+        istrust="$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid" org.kde.kdeconnect.device.isTrusted)"
+        if [ "$isreach" = "true" ] && [ "$istrust" = "true" ]
+        then
+            battery="$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid/battery" org.kde.kdeconnect.device.battery.charge)"
+            icon=$(get_icon "$battery" "$devicetype")
+            devices+="%{A1:$DIR/polybar-kdeconnect.sh -n '$devicename' -i $deviceid -b $battery -m:}$icon%{A}$SEPERATOR"
+        elif [ "$isreach" = "false" ] && [ "$istrust" = "true" ]
+        then
+            devices+="$(get_icon -1 "$devicetype")$SEPERATOR"
+        else
+            haspairing="$(qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$deviceid" org.kde.kdeconnect.device.hasPairingRequests)"
+            if [ "$haspairing" = "true" ]
+            then
+                show_pmenu2 "$devicename" "$deviceid"
+            fi
+            icon=$(get_icon -2 "$devicetype")
+            devices+="%{A1:$DIR/polybar-kdeconnect.sh -n $devicename -i $deviceid -p:}$icon%{A}$SEPERATOR"
+
+        fi
+    done
+    echo "${devices::-1}"
+}
+
+show_menu () {
+    menu="$(rofi -sep "|" -dmenu -i -p "$DEV_NAME" -location $LOCATION -yoffset $YOFFSET -xoffset $XOFFSET -theme $THEME -width $WIDTH -hide-scrollbar -line-padding 4 -padding 20 -lines 5 <<< "Battery: $DEV_BATTERY%|Ping|Find Device|Send File|Browse Files|Unpair")"
+                case "$menu" in
+                    *Ping) qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/ping" org.kde.kdeconnect.device.ping.sendPing ;;
+                    *'Find Device') qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/findmyphone" org.kde.kdeconnect.device.findmyphone.ring ;;
+                    *'Send File') qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/share" org.kde.kdeconnect.device.share.shareUrl "file://$(qarma --file-selection)" ;;
+                    *'Browse Files')
+                        if "$(qdbus --literal org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/sftp" org.kde.kdeconnect.device.sftp.isMounted)" == "false"; then
+                            qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/sftp" org.kde.kdeconnect.device.sftp.mount
+                        fi
+                        qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID/sftp" org.kde.kdeconnect.device.sftp.startBrowsing
+                        ;;
+                    *'Unpair' ) qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID" org.kde.kdeconnect.device.unpair
+                esac
+}
+
+show_pmenu () {
+    menu="$(rofi -sep "|" -dmenu -i -p "$DEV_NAME" -location $LOCATION -yoffset $YOFFSET -xoffset $XOFFSET -theme $THEME -width $WIDTH -hide-scrollbar -line-padding 1 -padding 20 -lines 1<<<"Pair Device")"
+                case "$menu" in
+                    *'Pair Device') qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$DEV_ID" org.kde.kdeconnect.device.requestPair
+                esac
+}
+
+show_pmenu2 () {
+    menu="$(rofi -sep "|" -dmenu -i -p "$1 has sent a pairing request" -location $LOCATION -yoffset $YOFFSET -xoffset $XOFFSET -theme $THEME -width $WIDTH_WIDE -hide-scrollbar -line-padding 4 -padding 20 -lines 2 <<< "Accept|Reject")"
+                case "$menu" in
+                    *'Accept') qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$2" org.kde.kdeconnect.device.acceptPairing ;;
+                    *) qdbus org.kde.kdeconnect "/modules/kdeconnect/devices/$2" org.kde.kdeconnect.device.rejectPairing
+                esac
+
+}
+get_icon () {
+    if [ "$2" = "tablet" ]
+    then
+        icon=$ICON_TABLET
+    else
+        icon=$ICON_SMARTPHONE
+    fi
+    case $1 in
+    "-1")     ICON="%{F$COLOR_DISCONNECTED}$icon%{F-}" ;;
+    "-2")     ICON="%{F$COLOR_NEWDEVICE}$icon%{F-}" ;;
+    5*)     ICON="%{F$COLOR_BATTERY_50}$icon%{F-}" ;;
+    6*)     ICON="%{F$COLOR_BATTERY_60}$icon%{F-}" ;;
+    7*)     ICON="%{F$COLOR_BATTERY_70}$icon%{F-}" ;;
+    8*)     ICON="%{F$COLOR_BATTERY_80}$icon%{F-}" ;;
+    9*|100) ICON="%{F$COLOR_BATTERY_90}$icon%{F-}" ;;
+    *)      ICON="%{F$COLOR_BATTERY_LOW}$icon%{F-}" ;;
+    esac
+    echo $ICON
+}
+
+unset DEV_ID DEV_NAME DEV_BATTERY
+while getopts 'di:n:b:mp' c
+do
+    # shellcheck disable=SC2220
+    case $c in
+        d) show_devices ;;
+        i) DEV_ID=$OPTARG ;;
+        n) DEV_NAME=$OPTARG ;;
+        b) DEV_BATTERY=$OPTARG ;;
+        m) show_menu  ;;
+        p) show_pmenu ;;
+    esac
+done
+
diff --git a/home-bin/qwalwal.sh b/home-bin/qwalwal.sh
new file mode 100644 (file)
index 0000000..d0bc3f8
--- /dev/null
@@ -0,0 +1,131 @@
+#! /bin/bash
+
+# walwal.sh - set wallpaper and global color theme using pywal
+
+# uncomment for debug
+# set -x
+
+PID=$$
+PIDFILE=${PIDFILE:-/tmp/walwal.pid}
+WAIT_CYCLE="15m"
+INPUT=$1
+
+trap "rm -f $PIDFILE" SIGTERM
+
+WP_SETTER="/usr/bin/wal"
+QARMA="/usr/bin/qarma"
+EXTRAS="/home/danix/bin/wal.sh"
+
+function set_wp() {
+       NEW_WP=$1
+       $WP_SETTER -i $NEW_WP -o $EXTRAS
+}
+
+# set background function (requires input)
+function wpapers() {
+       if [[ -f $1 ]]; then
+               # We have a single file as input
+               set_wp $1
+               exit 0
+       elif [[ -d $1 ]]; then
+               # directory as input
+               while true; do
+                       BGIMG=$(find $1 -type f -print | shuf -n1)
+                       set_wp ${BGIMG}
+                       sleep $WAIT_CYCLE
+               done
+               exit 0
+       fi
+}
+
+function file_or_dir() {
+       if [[ -f /tmp/choice ]]; then
+               rm /tmp/choice
+       fi
+
+       FOD=$($QARMA --list --text="single image or directory?" --hide-header "file" "directory" > /tmp/choice)
+       case $? in
+               0 )
+                       file_chooser $(cat /tmp/choice)
+                       ;;
+               1 )
+                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                       ;;
+               -1 )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function file_chooser() {
+       case $1 in
+               "file" )
+                       FILE="$($QARMA --file-selection --title='Choose your Wallpaper')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               "directory" )
+                       FILE="$($QARMA --file-selection --directory --title='Choose your Wallpaper directory')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               * )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function run() {
+       if [[ $(basename $0) == "walwal.sh" ]]; then
+               # we were called as walwal.sh, so simple wallpaper setter without qarma interaction
+               wpapers "$1"
+       elif [[ $(basename $0) == "qwalwal.sh" ]]; then
+               # we use qarma to set the wallpaper
+               $QARMA --question --title="Change Wallpaper" --text="Do you want to change the wallpaper?"
+               case $? in
+                       0 )
+                               file_or_dir
+                               ;;
+                       1 )
+                               $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                               ;;
+                       -1 )
+                               $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+               esac
+       fi
+}
+
+if [[ -r $PIDFILE ]]; then
+       # PIDFILE exists, so I guess there's already an instance running
+       # let's kill it and run again
+       kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
+       # should already be deleted by trap, but just to be sure
+       rm /tmp/choice || true
+       rm $PIDFILE
+fi
+
+# create PIDFILE
+echo $PID > $PIDFILE
+
+if [[ ! -z $1 ]]; then
+    run $1
+else
+    run
+fi
+
diff --git a/home-bin/run-polybar.sh b/home-bin/run-polybar.sh
new file mode 100644 (file)
index 0000000..e8d1a80
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+if [ ! -z $1 ]; then
+    case "$1" in
+        "off")
+            kill $(cat /tmp/hideIt-*.pid)
+            killall -q polybar
+            killall stalonetray
+            ;;
+        *)
+            false
+            ;;
+    esac
+else
+    # Terminate already running bar instances
+    killall -q polybar
+    # If all your bars have ipc enabled, you can also use
+    # polybar-msg cmd quit
+
+    # Launch Polybar, using default config location ~/.config/polybar/config.ini
+    polybar top -r --config=/home/danix/.config/polybar/config 2>&1 | tee -a /tmp/polybar-top.log & disown
+    polybar bottom -r --config=/home/danix/.config/polybar/config 2>&1 | tee -a /tmp/polybar-bottom.log & disown
+    echo "Polybar launched..."
+
+    # running stalonetray
+    # terminate previous instances
+    killall stalonetray
+
+    # launch stalonetray
+    stalonetray & disown
+
+    if [ -x ~/bin/hidebars.sh ]; then
+        sleep 1
+        ~/bin/hidebars.sh
+    fi
+fi
+
diff --git a/home-bin/send-to-device.sh b/home-bin/send-to-device.sh
new file mode 100644 (file)
index 0000000..a320e0f
--- /dev/null
@@ -0,0 +1,54 @@
+#! /bin/bash
+
+QARMA="/usr/bin/qarma"
+KDECONN=$(which kdeconnect-cli)
+CHOICE=/tmp/choose_devices
+OUTPUT=/tmp/share_output
+
+
+function scan_devices() {
+       if [[ -f $CHOICE ]]; then
+               rm $CHOICE
+       fi
+       DEV_NAMES=$(${KDECONN} -a --name-only)
+       DEV=$($QARMA --list --text="Select your device" --hide-header "$DEV_NAMES" > $CHOICE)
+       case $? in
+               0 )
+                       # echo "${KDECONN} --share figa -n \"$(cat $CHOICE)\""
+                       DEVICE="$(cat $CHOICE)"
+                       ;;
+               1 )
+                       $QARMA --info --title="Exiting!" --text="No device was selected."
+                       DEVICE=""
+                       ;;
+               -1 )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       DEVICE=""
+                       ;;
+       esac
+       printf %s "$DEVICE"
+}
+
+function send_file() {
+       if [[ -f $OUTPUT ]]; then
+               rm $OUTPUT
+       fi
+       if [[ -f $1 && -n $2 ]]; then
+               FILE=$1
+               DEST=$2
+               echo -e "Sharing to ${DEST} - " > $OUTPUT
+               $KDECONN --share "$FILE" -n "$DEST" 2>&1 >> $OUTPUT
+               $QARMA --info --title="Share to Device" --text="$(cat $OUTPUT)"
+       else
+               echo "no device selected"
+       fi
+}
+
+if [[ -f $1 ]]; then
+       # we have a file to share
+       SHARED=$1
+       SHAREDTO=$(scan_devices)
+       send_file "$SHARED" "$SHAREDTO"
+else
+       echo "tell me what to send"
+fi
diff --git a/home-bin/slack-updates b/home-bin/slack-updates
new file mode 100644 (file)
index 0000000..080dd2f
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/bash
+CL="https://mirrors.slackware.com/slackware/slackware64-current/ChangeLog.txt"
+LOCAL_CL="/var/lib/slackpkg/ChangeLog.txt"
+TMP="/tmp"
+TMPCL="${TMP}/ChangeLog.txt"
+
+
+function SHOW_TOOLTIP () {
+       sed '/+--------------------------+/Q' $TMPCL |qarma --width=600 --height=400 --text-info
+}
+
+
+if [[ -f ${TMPCL} ]]; then
+       rm $TMPCL
+fi
+       wget -q -P ${TMP} ${CL}
+       if [ $? -ne 0 ]; then
+               echo "  󱧖   "
+       fi
+NCL=$(head -n 1 ${TMPCL})
+LCL=$(head -n 1 ${LOCAL_CL})
+
+if [[ ${NCL} != ${LCL} ]];then
+       echo "  󰏗 󰚰 "
+else
+       echo "  󰏗   "
+fi
+
+if [ $1 ]; then
+       case $1 in
+               -n )
+                       SHOW_TOOLTIP
+                       ;;
+       esac
+fi
+
diff --git a/home-bin/speedtest b/home-bin/speedtest
new file mode 100644 (file)
index 0000000..44c03da
Binary files /dev/null and b/home-bin/speedtest differ
diff --git a/home-bin/superenalotto.sh b/home-bin/superenalotto.sh
new file mode 100644 (file)
index 0000000..fe3af27
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+#declare -A numbers
+#declare -a sorted_numbers
+
+#for i in {1..6}
+#do
+#  while true
+#  do
+#    number=$((RANDOM % 90 + 1))
+#    if [[ ! -v numbers[$number] ]]; then
+#      numbers[$number]=1
+#      break
+#    fi
+#  done
+#done
+
+#for key in "${!numbers[@]}"
+#do
+#  sorted_numbers+=("$key")
+#done
+
+#IFS=$'\n' sorted_numbers=($(sort <<<"${sorted_numbers[*]}"))
+#unset IFS
+
+#for number in "${sorted_numbers[@]}"
+#do
+#  echo $number
+#done
+
+
+# Genera 6 numeri casuali compresi tra 1 e 90
+numbers=$(shuf -i 1-90 -n 6 | sort -n)
+
+# Stampa i numeri in ordine crescente
+echo "I numeri casuali sono: $numbers"
+
diff --git a/home-bin/symbols.sh b/home-bin/symbols.sh
new file mode 100644 (file)
index 0000000..d97e179
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+recently_used_file=~/.cache/recent-symbols
+symbols_folder=~/.config/rofi/rofi-symbols
+
+rofi_copy=false
+
+main() {
+    prompt="symbols"
+
+    if $rofi_copy
+    then
+        prompt="$prompt (copy)"
+    fi
+    echo -en "\0prompt\x1f$prompt\n"
+
+    recent=""
+    if [[ -f "$recently_used_file" ]]
+    then
+        recent=$(cat "$recently_used_file")
+    fi
+
+    if [[ "$*" ]]
+    then
+        output=$(echo "$*" | sed 's/ .*$//')
+
+        if $rofi_copy
+        then
+            coproc { sleep 0.1; \
+                printf "%s""$output" | xclip -selection clipboard; }
+        else
+            coproc { sleep 0.3; \
+                xdotool type "$output" > /dev/null; }
+        fi
+        
+        first=$(echo "$recent" | head -n 1 | sed 's/ .*$//')
+
+        if [[ "$first" != "$output" ]]
+        then
+            new_recent=""
+            if [[ "$recent" == *"$output"* ]]
+            then
+                new_recent=$(printf "%s\n" "$*"; echo "$recent")
+                new_recent=$(echo "$new_recent" | awk '! a[$0]++')
+            else
+                new_recent=$(printf "%s\n" "$*"; echo "$recent")
+            fi
+
+            echo "$new_recent" | head -n 5 > "$recently_used_file"
+        fi
+
+        exit 0
+    fi
+
+    display_recent=""
+    if [[ "$recent" ]]
+    then
+        display_recent=$(printf "%s\n" "$recent" | head -n 5; \
+            printf "%s" "\n ""\0""nonselectable\x1ftrue\n")
+    fi
+
+    display_symbols=$(cat "$symbols_folder"/*.symbols | grep "^[^#]")
+
+    if [[ "$display_symbols" ]]
+    then
+        echo -en "$display_recent""$display_symbols"\
+            | awk '! a[$0]++'
+    else
+        echo -en "\0message\x1fno symbols found in ""$symbols_folder"
+    fi
+}
+
+if [[ "$1" == "copy" ]]
+then
+    rofi_copy=true
+    main "${@:2}"
+else
+    rofi_copy=false
+    main "$@"
+fi
diff --git a/home-bin/vt-color-scheme.py b/home-bin/vt-color-scheme.py
new file mode 100644 (file)
index 0000000..de7116d
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Usage:
+# 1. Save as `vt-color-scheme.py`
+# 2. Make executable
+# 3. Go to https://terminal.sexy and make your own color scheme
+# 4. Export as JSON (easier to work with) and edit TANGO_SCHEME
+#    with the colors you chose
+# 5. Run this script (redirect to text file if it's easier for you)
+# 6. Paste the output as boot option in Lilo / Elilo etc.
+
+KERNEL_PARAMS = ['vt.default_red', 'vt.default_grn', 'vt.default_blu', ]
+
+# MATERIAL DARK theme
+# https://material-ui.com/customization/default-theme/
+TANGO_SCHEME = [
+    "#303030",
+    "#d32f2f",
+    "#388e3c",
+    "#f57c00",
+    "#303f9f",
+    "#c51162",
+    "#1976d2",
+    "#bdbdbd",
+    "#424242",
+    "#e57373",
+    "#81c784",
+    "#ffb74d",
+    "#7986cb",
+    "#ff4081",
+    "#64b5f6",
+    "#f5f5f5"
+  ]
+
+def color_scheme_to_rgb_channel_data(color_scheme):
+    """
+    [ '#r1g1b1', '#r2g2b2', ... ] => [ [r1, r2, ...], [g1, g2, ...], [b1, b2, ...], ]
+    """
+
+    def split_to_rgb(color):
+        # rgb str -> [red, green, blue]
+        hex_value = int(color, 16)
+        return [hex_value >> 16 & 255, hex_value >> 8 & 255, hex_value & 255]
+
+    return zip(*[split_to_rgb(color.strip('#')) for color in color_scheme])
+
+
+def prepare_param(param, channel_data):
+    return '{param}={values}'.format(param=param, values=','.join(hex(byte) for byte in channel_data))
+
+
+def main(colors):
+    print(' '.join(prepare_param(param, channel) for param, channel in \
+                   zip(KERNEL_PARAMS, color_scheme_to_rgb_channel_data(colors))))
+
+
+if __name__ == '__main__':
+    main(TANGO_SCHEME)
diff --git a/home-bin/wacom b/home-bin/wacom
new file mode 100644 (file)
index 0000000..0cf66e3
--- /dev/null
@@ -0,0 +1,22 @@
+#! /bin/bash
+
+device="Wacom Intuos PT M"
+PAD="$device Pad pad"
+FINGER="$device Finger touch" # NOT USED
+STYLUS="$device Pen stylus"
+ERASER="$device Pen eraser"
+
+xsetwacom set "$PAD" "Button" 1 "key +ctrl e -ctrl"
+xsetwacom set "$PAD" "Button" 3 "key +ctrl s -ctrl"
+xsetwacom set "$PAD" "Button" 8 "key +ctrl y -ctrl"
+xsetwacom set "$PAD" "Button" 9 "key +ctrl z -ctrl"
+
+xsetwacom set "$STYLUS" PressureCurve 5 10 90 95
+xsetwacom set "$STYLUS" "Button" 1 "button +1"
+xsetwacom set "$STYLUS" "Button" 2 "button +2 -2"
+xsetwacom set "$STYLUS" "Button" 3 "button +3 -3"
+
+xsetwacom set "$ERASER" PressureCurve 0 10 90 100
+xsetwacom set "$ERASER" "Button" 1 key "button +1"
+xsetwacom set "$ERASER" "Button" 2 key ""
+xsetwacom set "$ERASER" "Button" 3 key ""
diff --git a/home-bin/wal.sh b/home-bin/wal.sh
new file mode 100644 (file)
index 0000000..079c88c
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# Symlink dunst config
+ln -sf ~/.cache/wal/dunstrc ~/.config/dunst/dunstrc
+# Restart dunst with the new color scheme
+pkill dunst
+dunst &
+
+# restart stalonetray
+pkill stalonetray
+stalonetray &
+
+# run walogram to update telegram theme
+~/bin/walogram &
+
+# update firefox theme
+pywalfox update
+
+# link background image to be used by firefox
+IMG=$(cat ~/.cache/wal/wal)
+FNAME=$(basename -- $IMG)
+EXT="${FNAME##*.}"
+ln -sf $IMG ~/.cache/wal/wpaper.${EXT}
+
+kill $(cat /tmp/hideIt-*.pid) || true
+sleep 1
+hideIt.sh --name "^polybar-top_VGA-1$" --hover --peek 3 --direction top --steps 25 & disown
+hideIt.sh --name "^polybar-bottom_VGA-1$" --hover --peek 3 --direction bottom --steps 25 & disown
+hideIt.sh --name "^stalonetray$" --region 0x120+0+300 --peek 3 --direction left --steps 25 & disown
+
diff --git a/home-bin/wallpaper.sh b/home-bin/wallpaper.sh
new file mode 100644 (file)
index 0000000..e3d293a
--- /dev/null
@@ -0,0 +1,127 @@
+#! /bin/bash
+
+# uncomment for debug
+# set -x
+
+PID=$$
+PIDFILE=${PIDFILE:-/tmp/wallpaper.pid}
+WAIT_CYCLE="5m"
+INPUT=$1
+
+trap "rm -f $PIDFILE" SIGTERM
+
+WP_SETTER="/usr/bin/feh"
+QARMA="/usr/bin/qarma"
+
+function set_wp() {
+       NEW_WP=$1
+       $WP_SETTER --bg-fill $NEW_WP
+}
+
+# set background function (requires input)
+function wpapers() {
+       if [[ -f $1 ]]; then
+               # We have a single file as input
+               set_wp $1
+               exit 0
+       elif [[ -d $1 ]]; then
+               # directory as input
+               while true; do
+                       BGIMG=$(find $1 -type f -print | shuf -n1)
+                       set_wp ${BGIMG}
+                       sleep $WAIT_CYCLE
+               done
+       fi
+}
+
+function file_or_dir() {
+       if [[ -f /tmp/choice ]]; then
+               rm /tmp/choice
+       fi
+
+       FOD=$($QARMA --list --text="single image or directory?" --hide-header "file" "directory" > /tmp/choice)
+       case $? in
+               0 )
+                       file_chooser $(cat /tmp/choice)
+                       ;;
+               1 )
+                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                       ;;
+               -1 )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function file_chooser() {
+       case $1 in
+               "file" )
+                       FILE="$($QARMA --file-selection --title='Choose your Wallpaper')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               "directory" )
+                       FILE="$($QARMA --file-selection --directory --title='Choose your Wallpaper directory')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               * )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function run() {
+       if [[ $(basename $0) == "wallpaper.sh" ]]; then
+               # we were called as wallpaper.sh, so simple wallpaper setter without qarma interaction
+               wpapers "$1"
+       elif [[ $(basename $0) == "change_wallpaper.sh" ]]; then
+               # we use qarma to set the wallpaper
+               $QARMA --question --title="Change Wallpaper" --text="Do you want to change the wallpaper?"
+               case $? in
+                       0 )
+                               file_or_dir
+                               ;;
+                       1 )
+                               $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                               ;;
+                       -1 )
+                               $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+               esac
+       fi
+}
+
+if [[ -r $PIDFILE ]]; then
+       # PIDFILE exists, so I guess there's already an instance running
+       # let's kill it and run again
+       kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
+       # should already be deleted by trap, but just to be sure
+       rm /tmp/choice || true
+       rm $PIDFILE
+fi
+
+# create PIDFILE
+echo $PID > $PIDFILE
+
+if [[ ! -z $1 ]]; then
+    run $1
+else
+    run
+fi
+
diff --git a/home-bin/walogram b/home-bin/walogram
new file mode 100644 (file)
index 0000000..0229630
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/env bash
+
+# walogram - generate telegram-desktop themes
+# based on generated by wal or user defined colors
+
+# originally written by Guillaume Boehm <guillaumeboehm@hotmail.fr> in 2021
+# rewritten by Zubarev Grigoriy <thirtysix@thirtysix.pw> in 2023
+# this program is provided free of charge and without warranty
+
+cachedir="${XDG_CACHE_HOME:-$HOME/.cache}/walogram"
+themename="wal.tdesktop-theme"
+constants="/usr/local/share/walogram/constants.tdesktop-theme"
+
+msg() {
+       printf "\e[1;34m ::\e[0m %s\\n" "$@"
+}
+
+error() {
+       printf "\e[1;31m ::\e[0m %s\\n" "$@" >&2
+       exit 1
+}
+
+gencolors() {
+       colors="0 1 2 3 4 5 7 8 9 10 11 12 13 14 15"
+       divisions="10 20 30 40 50 60 70 80 90"
+       alphas="00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee"
+
+       for i in $colors; do
+               color="color$i"
+               c_rgb_12d=$((0x"${!color:1:2}"))
+               c_rgb_34d=$((0x"${!color:3:2}"))
+               c_rgb_56d=$((0x"${!color:5:2}"))
+
+               for division in $divisions; do
+                       # lighter variants
+                       c_r=$((c_rgb_12d + (c_rgb_12d * (division / 10) / 10)))
+                       c_g=$((c_rgb_34d + (c_rgb_34d * (division / 10) / 10)))
+                       c_b=$((c_rgb_56d + (c_rgb_56d * (division / 10) / 10)))
+                       [ "$c_r" -ge 255 ] && c_r=255
+                       [ "$c_g" -ge 255 ] && c_g=255
+                       [ "$c_b" -ge 255 ] && c_b=255
+                       c_hex_r="$(printf "%x" "$c_r")"
+                       c_hex_g="$(printf "%x" "$c_g")"
+                       c_hex_b="$(printf "%x" "$c_b")"
+                       [ "${#c_hex_r}" -eq 1 ] && c_hex_r="0${c_hex_r}"
+                       [ "${#c_hex_g}" -eq 1 ] && c_hex_g="0${c_hex_g}"
+                       [ "${#c_hex_b}" -eq 1 ] && c_hex_b="0${c_hex_b}"
+                       c_hex="#${c_hex_r}${c_hex_g}${c_hex_b}"
+                       eval "color${i}_lighter_${division}=${c_hex}"
+
+                       # darker variants
+                       c_r=$((c_rgb_12d - (c_rgb_12d * (division / 10) / 10)))
+                       c_g=$((c_rgb_34d - (c_rgb_34d * (division / 10) / 10)))
+                       c_b=$((c_rgb_56d - (c_rgb_56d * (division / 10) / 10)))
+                       c_hex_r="$(printf "%x" "$c_r")"
+                       c_hex_g="$(printf "%x" "$c_g")"
+                       c_hex_b="$(printf "%x" "$c_b")"
+                       [ "${#c_hex_r}" -eq 1 ] && c_hex_r="0${c_hex_r}"
+                       [ "${#c_hex_g}" -eq 1 ] && c_hex_g="0${c_hex_g}"
+                       [ "${#c_hex_b}" -eq 1 ] && c_hex_b="0${c_hex_b}"
+                       c_hex="#${c_hex_r}${c_hex_g}${c_hex_b}"
+                       eval "color${i}_darker_${division}=${c_hex}"
+               done
+       done
+
+       for i in $colors; do
+               echo "// color$i: original"
+               echo "color$i: $(eval "echo \"\$color${i}\"");"
+
+               echo "// color$i: lighter and darker variants"
+               for division in $divisions; do
+                       echo "colorLighter${i}_${division}: $(eval "echo \"\$color${i}_lighter_${division}\"");"
+                       echo "colorDarker${i}_${division}: $(eval "echo \"\$color${i}_darker_${division}\"");"
+               done
+
+               echo "// color$i: alpha variants"
+               for alpha in $alphas; do
+                       echo "colorAlpha${i}_${alpha}: $(eval "echo \"\${color${i}}${alpha}\"");"
+               done
+               printf "\\n"
+       done
+}
+
+gentheme() {
+       walname="background.jpg"
+       tempdir="$(mktemp -d)"
+       trap 'rm -rf "$tempdir"; exit 0' INT TERM EXIT
+
+       gencolors | cat - "$constants" >"$tempdir/colors.tdesktop-theme"
+
+       if command -v zip >/dev/null 2>&1; then
+               if [ "$walmode" = "solid" ]; then
+                       # $background is set by wal in colors.sh template
+                       convert -size 256x256 "xc:${bgcolor:-${background:-$color0}}" "$tempdir/$walname"
+               else
+                       case "$(file -b --mime-type "$wal")" in
+                       image/*) convert ${blur:+-blur 0x16} -resize 1920x1080 "$wal" "$tempdir/$walname" ;;
+                       *) error "not an image: $wal" ;;
+                       esac
+               fi
+               # syncing files (with '-FS') in an existing archive is faster than creating new
+               zip -jq -FS "$cachedir/$themename" "$tempdir"/*
+       else
+               msg "'zip' not found. theme generated without background image"
+               cp -f "$tempdir/colors.tdesktop-theme" "$cachedir/$themename"
+       fi
+}
+
+main() {
+       walmode="background"
+       theme="${XDG_CACHE_HOME:-$HOME/.cache}/wal/colors.sh"
+       read -r wal <"${XDG_CACHE_HOME:-$HOME/.cache}/wal/wal"
+
+       while getopts ":vhi:f:b:Bcs" opt; do
+               case "$opt" in
+               v)
+                       printf "walogram-VERSION\\n"
+                       exit 0
+                       ;;
+               h)
+                       man walogram || error "could not open man page"
+                       exit 0
+                       ;;
+               b)
+                       printf "%s" "$OPTARG" | grep -Eq "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" || error "not a valid color code: $OPTARG"
+                       bgcolor="$OPTARG"
+                       walmode="solid"
+                       ;;
+               i) wal="$OPTARG" ;;
+               f) theme="$OPTARG" ;;
+               B) blur="true" ;;
+               c) rm -f "$cachedir"/* && msg "cache cleared" ;;
+               s) walmode="solid" ;;
+               :) error "'-$OPTARG' requires argument" ;;
+               \?) error "invalid option: '-$OPTARG'" ;;
+               esac
+       done
+
+       [ ! -f "$theme" ] && error "theme not found: $theme"
+       [ ! -f "$wal" ] && error "background image not found: $wal"
+
+       # shellcheck source=/dev/null
+       . "$theme" >/dev/null 2>&1
+       msg "colors loaded: $theme"
+
+       # ensure all 16 colors are set
+       for i in 0 1 2 3 4 5 6 7; do
+               normal="color$i"
+               bright="color$((i + 8))"
+               [ -z "$(eval "echo \"\$$normal\"")" ] && error "$normal value is missing"
+               [ -z "$(eval "echo \"\$$bright\"")" ] && {
+                       msg "$normal value used for $bright, because it is unset"
+                       eval "$bright=\$$normal"
+               }
+       done
+
+       gentheme && msg "theme generated: $themename"
+}
+
+[ ! -f "$constants" ] && error "constants file not found: $constants"
+[ -e "$cachedir" ] && [ ! -d "$cachedir" ] && error "cache directory location already exists and is not a directory: $cachedir"
+mkdir -p "$cachedir" || error "unable to create cache directory: $cachedir"
+[ ! -d "${XDG_CACHE_HOME:-$HOME/.cache}/wal" ] && error "wal cache directory not found: ${XDG_CACHE_HOME:-$HOME/.cache}/wal"
+
+if command -v magick >/dev/null 2>&1; then
+       convert() { magick convert "$@"; }
+elif ! command -v convert >/dev/null 2>&1; then
+       error "'convert' from ImageMagick not found"
+fi
+
+main "$@"
+
+# vim: ts=4 sw=4 noet ft=sh
diff --git a/home-bin/walwal.sh b/home-bin/walwal.sh
new file mode 100644 (file)
index 0000000..d0bc3f8
--- /dev/null
@@ -0,0 +1,131 @@
+#! /bin/bash
+
+# walwal.sh - set wallpaper and global color theme using pywal
+
+# uncomment for debug
+# set -x
+
+PID=$$
+PIDFILE=${PIDFILE:-/tmp/walwal.pid}
+WAIT_CYCLE="15m"
+INPUT=$1
+
+trap "rm -f $PIDFILE" SIGTERM
+
+WP_SETTER="/usr/bin/wal"
+QARMA="/usr/bin/qarma"
+EXTRAS="/home/danix/bin/wal.sh"
+
+function set_wp() {
+       NEW_WP=$1
+       $WP_SETTER -i $NEW_WP -o $EXTRAS
+}
+
+# set background function (requires input)
+function wpapers() {
+       if [[ -f $1 ]]; then
+               # We have a single file as input
+               set_wp $1
+               exit 0
+       elif [[ -d $1 ]]; then
+               # directory as input
+               while true; do
+                       BGIMG=$(find $1 -type f -print | shuf -n1)
+                       set_wp ${BGIMG}
+                       sleep $WAIT_CYCLE
+               done
+               exit 0
+       fi
+}
+
+function file_or_dir() {
+       if [[ -f /tmp/choice ]]; then
+               rm /tmp/choice
+       fi
+
+       FOD=$($QARMA --list --text="single image or directory?" --hide-header "file" "directory" > /tmp/choice)
+       case $? in
+               0 )
+                       file_chooser $(cat /tmp/choice)
+                       ;;
+               1 )
+                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                       ;;
+               -1 )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function file_chooser() {
+       case $1 in
+               "file" )
+                       FILE="$($QARMA --file-selection --title='Choose your Wallpaper')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               "directory" )
+                       FILE="$($QARMA --file-selection --directory --title='Choose your Wallpaper directory')"
+                       case $? in
+                               0 )
+                                       wpapers $FILE
+                                       ;;
+                               1 )
+                                       $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                                       ;;
+                               -1 )
+                                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       esac
+                       ;;
+               * )
+                       $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+                       ;;
+       esac
+}
+
+function run() {
+       if [[ $(basename $0) == "walwal.sh" ]]; then
+               # we were called as walwal.sh, so simple wallpaper setter without qarma interaction
+               wpapers "$1"
+       elif [[ $(basename $0) == "qwalwal.sh" ]]; then
+               # we use qarma to set the wallpaper
+               $QARMA --question --title="Change Wallpaper" --text="Do you want to change the wallpaper?"
+               case $? in
+                       0 )
+                               file_or_dir
+                               ;;
+                       1 )
+                               $QARMA --info --title="Exiting!" --text="No wallpaper was changed."
+                               ;;
+                       -1 )
+                               $QARMA --error --title="Ooops!!" --text="Something unexpected happened."
+               esac
+       fi
+}
+
+if [[ -r $PIDFILE ]]; then
+       # PIDFILE exists, so I guess there's already an instance running
+       # let's kill it and run again
+       kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
+       # should already be deleted by trap, but just to be sure
+       rm /tmp/choice || true
+       rm $PIDFILE
+fi
+
+# create PIDFILE
+echo $PID > $PIDFILE
+
+if [[ ! -z $1 ]]; then
+    run $1
+else
+    run
+fi
+
diff --git a/home-config/i3/config b/home-config/i3/config
new file mode 100644 (file)
index 0000000..be93a6d
--- /dev/null
@@ -0,0 +1,352 @@
+# i3 config file (v4)
+#
+# Please see http://i3wm.org/docs/userguide.html for a complete reference!
+
+#########################################################################
+#                             MACROPAD                                  #
+#                                                                       #
+#                                                                       #
+#       Win+w           Win+r           Win+g           Shift+Ctrl+l    #
+#                                                                       #
+#                                                                       #
+#       Alt+Return      Alt+h           Ctrl+Win+r      Alt+w           #
+#                                                                       #
+#                                                                       #
+#       Ctrl+F1         Ctrl+F1         Ctrl+F1         Ctrl+F1         #
+#                                                                       #
+#                                                                       #
+#                                                                       #
+#########################################################################
+
+###########################
+# xbindkeys -k            #
+# xprop | grep -i 'class' #
+###########################
+
+# reload the configuration file
+bindsym $alt+Shift+c reload
+
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym $alt+Shift+r restart
+
+# set modifiers
+##
+##      Win to be used to issue commands application related, like launching programs
+##      Alt to be used to issue commands desktop related, like modifying layout of windows
+##      Ctrl in combination with the other two, to issue special commands.
+set $win Mod4
+set $alt Mod1
+set $ctrl Ctrl
+
+# browser variable
+set $browser firefox
+set $incognito-browser firefox --private-window
+# editor variable
+set $prog-editor sublime_text
+
+# Workspace names
+# # to display names or symbols instead of plain workspace numbers you can use
+# # something like: set $ws1 1:mail
+# #                 set $ws2 2:x
+#
+set $ws1 "web"
+set $ws2 "editor"
+set $ws3 "console"
+set $ws4 "ssh"
+set $ws5 "graphic"
+set $ws6 "editor2"
+set $ws7 "chat"
+set $ws8 "gaming"
+
+# workspace back and forth
+workspace_auto_back_and_forth yes
+
+# per workspace gaps
+workspace $ws1 gaps inner 0
+workspace $ws1 gaps outer 0
+workspace $ws2 gaps inner 0
+workspace $ws2 gaps outer 0
+workspace $ws8 gaps inner 0
+workspace $ws8 gaps outer 0
+
+
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below.
+font pango:"Droid Sans 10"
+
+# Use Mouse+$win to drag floating windows to their wanted position
+floating_modifier $win
+
+# start a terminal
+# macropad 5
+bindsym $alt+Return exec --no-startup-id kitty
+# macropad Shift+5
+bindsym Shift+$alt+Return exec --no-startup-id blackpearl-sshmenu.sh
+
+
+# custom binding
+# macropad 4
+bindsym Shift+$ctrl+l exec --no-startup-id clipmenu -no-lazy-grab -show run -theme darknix/notes
+# macropad Shift+4
+bindsym $alt+Shift+$ctrl+l exec --no-startup-id xclip -o | qrencode -o - | feh -Z -
+
+bindsym Shift+$win+s exec --no-startup-id blackpearl-scrotmenu.sh
+bindsym $alt+Menu exec --no-startup-id qwalwal.sh
+bindsym $win+period exec --no-startup-id blackpearl-emoji.sh
+bindsym Shift+$win+period exec --no-startup-id blackpearl-symbols.sh
+bindsym XF86Search exec --no-startup-id blackpearl-appsmenu.sh
+bindsym $alt+Escape --release exec xkill
+bindsym $win+e exec pcmanfm-qt
+
+# macropad 3
+bindsym $win+g exec gimp
+
+bindsym $win+l exec --no-startup-id betterlockscreen -l
+
+# macropad 1
+bindsym $win+w exec $browser
+# macropad Shift+1
+bindsym $win+Shift+w exec $incognito-browser
+
+bindsym XF86Calculator exec kcalc
+bindsym XF86Sleep exec i3suspend
+# macropad 2
+bindsym $win+r exec $prog-editor
+# macropad Shift+2
+bindsym $win+Shift+r exec Typora
+
+bindsym XF86HomePage exec --no-startup-id blackpearl-appsmenu.sh
+#bindsym $win+p exec uxterm -T "spt" -geometry 90x30+800+30 -e spt
+bindsym $win+m exec Telegram
+
+# notes bindings
+bindsym $ctrl+F5 exec --no-startup-id blackpearl-notes.sh -a
+bindsym $ctrl+F6 exec --no-startup-id blackpearl-notes.sh -e
+bindsym $ctrl+F7 exec --no-startup-id blackpearl-notes.sh -d
+bindsym $ctrl+F8 exec --no-startup-id blackpearl-notes.sh
+
+# window switching bindings
+bindsym $alt+Tab exec --no-startup-id "blackpearl-window.sh all"
+#bindsym $alt+Tab exec --no-startup-id "killall -s SIGUSR1 i3expod"
+#bindsym $alt+Ctrl+Tab exec --no-startup-id "blackpearl-window.sh all"
+
+# multimedia binding
+# macropad top encoder
+bindsym XF86AudioMute exec --no-startup-id dunst_vol_brig.sh mute
+bindsym XF86AudioLowerVolume exec --no-startup-id dunst_vol_brig.sh vol_d
+bindsym XF86AudioRaiseVolume exec --no-startup-id dunst_vol_brig.sh vol_u
+# macropad bottom encoder
+bindsym XF86AudioPlay exec --no-startup-id playerctl play-pause
+bindsym XF86AudioPrev exec --no-startup-id playerctl previous
+bindsym XF86AudioNext exec --no-startup-id playerctl next
+
+# brightness bindings
+# macropad Alt + top encoder
+bindsym $alt+XF86AudioLowerVolume exec --no-startup-id dunst_vol_brig.sh brig_d
+bindsym $alt+XF86AudioRaiseVolume exec --no-startup-id dunst_vol_brig.sh brig_u
+
+# kill focused window
+#bindsym $ctrl+Shift+q kill
+bindsym --whole-window $alt+button2 kill
+
+# restart polybar
+# macropad Alt + bottom encoder
+bindsym $alt+XF86AudioPlay exec --no-startup-id polybar-msg cmd restart
+
+# start rofi in dmenu mode (a program launcher)
+bindsym $alt+F2 exec --no-startup-id blackpearl-runner.sh
+
+# change focus
+bindsym $alt+j focus left
+bindsym $alt+k focus down
+bindsym $alt+l focus up
+bindsym $alt+ograve focus right
+
+# alternatively, you can use the cursor keys:
+bindsym $alt+Left focus left
+bindsym $alt+Down focus down
+bindsym $alt+Up focus up
+bindsym $alt+Right focus right
+
+# move focused window
+bindsym $alt+Shift+j move left
+bindsym $alt+Shift+k move down
+bindsym $alt+Shift+l move up
+bindsym $alt+Shift+ograve move right
+
+# alternatively, you can use the cursor keys:
+bindsym $alt+Shift+Left move left
+bindsym $alt+Shift+Down move down
+bindsym $alt+Shift+Up move up
+bindsym $alt+Shift+Right move right
+
+# split in horizontal orientation
+bindsym $alt+h exec notify-send 'splitting windows horizontally';split h
+# split in vertical orientation
+bindsym Shift+$alt+h exec notify-send 'splitting windows vertically';split v
+
+# enter fullscreen mode for the focused container
+bindsym $alt+f fullscreen toggle
+
+# change container layout (stacked, tabbed, toggle split)
+bindsym $alt+w exec notify-send 'layout tabbed';layout tabbed
+bindsym Shift+$alt+w exec notify-send 'layout stacking';layout stacking
+bindsym Ctrl+$alt+w exec notify-send 'layout split';layout toggle split
+
+# toggle tiling / floating
+bindsym $alt+Shift+space floating toggle
+
+# scratchpad
+bindsym $win+d move scratchpad
+bindsym Shift+$win+d scratchpad show
+
+# change focus between tiling / floating windows
+bindsym $alt+space focus mode_toggle
+
+# focus the parent container
+bindsym $alt+a focus parent
+
+# focus the children container
+bindsym $alt+b focus child
+
+# focus the child container
+#bindsym $win+d focus child
+
+# switch to workspace
+bindsym Ctrl+F1 workspace $ws1
+bindsym Ctrl+F2 workspace $ws2
+bindsym Ctrl+F3 workspace $ws3
+bindsym Ctrl+F4 workspace $ws4
+bindsym Shift+Ctrl+F1 workspace $ws5
+bindsym Shift+Ctrl+F2 workspace $ws6
+bindsym Shift+Ctrl+F3 workspace $ws7
+bindsym Shift+Ctrl+F4 workspace $ws8
+
+# move focused container to workspace
+bindsym $win+Ctrl+F1 move container to workspace $ws1
+bindsym $win+Ctrl+F2 move container to workspace $ws2
+bindsym $win+Ctrl+F3 move container to workspace $ws3
+bindsym $win+Ctrl+F4 move container to workspace $ws4
+bindsym $win+Shift+Ctrl+F1 move container to workspace $ws5
+bindsym $win+Shift+Ctrl+F2 move container to workspace $ws6
+bindsym $win+Shift+Ctrl+F3 move container to workspace $ws7
+bindsym $win+Shift+Ctrl+F4 move container to workspace $ws8
+
+# navigate workspaces
+#bindsym $win+Home workspace $ws1
+#bindsym $win+End workspace $ws8
+bindsym $win+XF86AudioPrev workspace prev
+bindsym $win+XF86AudioNext workspace next
+
+# assign programs to specific workspaces
+assign [class="firefox"] $ws1
+assign [class="PrusaSlicer"] $ws5
+assign [class="Sublime_text"] $ws2 
+for_window [title="^ssh*"] move to workspace $ws4
+assign [class="Alacritty"] $ws3
+assign [class="kitty"] $ws3
+assign [class="Gimp"] $ws5
+assign [class="Typora"] $ws6
+assign [class="Joplin"] $ws6
+assign [class="Telegram"] $ws7
+assign [class="steam"] $ws8
+assign [class="xemu"] $ws8
+assign [class="duckstation-qt"] $ws8
+for_window [title="soma"] move to workspace $ws8
+
+# open specific apps in floating mode
+for_window [class="kcalc"] floating enable border pixel 1
+for_window [class="Blueman-manager"] floating enable border pixel 1
+for_window [class="lxsudo"] floating enable border pixel 1
+for_window [title="^spt$"] floating enable border pixel 1
+for_window [title="^notes$"] floating enable border pixel 1
+for_window [class="org.nickvision.cavalier"] floating enable border pixel 1
+
+# cavalier sticks to all desktops
+for_window [class="org.nickvision.cavalier"] sticky enable
+
+# steam starts in tabbed layout
+#for_window [class="steam"] layout tabbed
+
+# switch to workspace with urgent window automatically
+for_window [urgent=latest] focus
+
+client.focused #f57c00 #ffb74d #303030 #7986cb
+client.focused_inactive #303030 #303030 #ffb74d #303030
+client.unfocused #303030 #303030 #BDBDBD #303030
+client.urgent #D32F2F #D32F2F #303030 #D32F2F
+
+# window borders
+hide_edge_borders both
+for_window [class="^.*"] border pixel 0
+
+gaps outer 0
+gaps inner 10
+workspace $ws1 gaps inner 0
+workspace $ws2 gaps inner 0
+workspace $ws5 gaps inner 0
+
+# Set shut down, restart and locking features
+bindsym $win+x exec --no-startup-id blackpearl-powermenu.sh
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+        # These bindings trigger as soon as you enter the resize mode
+
+        bindsym j resize shrink width 10 px or 10 ppt
+        bindsym k resize grow height 10 px or 10 ppt
+        bindsym l resize shrink height 10 px or 10 ppt
+        bindsym ograve resize grow width 10 px or 10 ppt
+
+        # same bindings, but for the arrow keys
+        bindsym Left resize shrink width 10 px or 10 ppt
+        bindsym Down resize grow height 10 px or 10 ppt
+        bindsym Up resize shrink height 10 px or 10 ppt
+        bindsym Right resize grow width 10 px or 10 ppt
+
+        # back to normal: Enter or Escape
+        bindsym Return exec notify-send 'Resize Mode OFF';mode "default"
+        bindsym Escape exec notify-send 'Resize Mode OFF';mode "default"
+}
+
+bindsym $win+Ctrl+r exec notify-send 'Resize Mode';mode "resize"
+
+set $wine_gaps_outer Outer Gaps: +|-|0 (local), Shift + +|-|0 (global)
+set $wine_gaps_inner Inner Gaps: +|-|0 (local), Shift + +|-|0 (global)
+bindsym $win+Shift+g mode "$wine_gaps"
+
+mode "$wine_gaps" {
+        bindsym o      mode "$wine_gaps_outer"
+        bindsym i      mode "$wine_gaps_inner"
+        bindsym Return mode "$wine_gaps"
+        bindsym Escape mode "default"
+}
+
+
+mode "$wine_gaps_outer" {
+        bindsym plus  gaps outer current plus 5
+        bindsym minus gaps outer current minus 5
+        bindsym 0     gaps outer current set 0
+
+        bindsym Shift+plus  gaps outer all plus 5
+        bindsym Shift+minus gaps outer all minus 5
+        bindsym Shift+0     gaps outer all set 0
+
+        bindsym Return mode "$wine_gaps"
+        bindsym Escape mode "default"
+}
+mode "$wine_gaps_inner" {
+        bindsym plus  gaps inner current plus 5
+        bindsym minus gaps inner current minus 5
+        bindsym 0     gaps inner current set 0
+
+        bindsym Shift+plus  gaps inner all plus 5
+        bindsym Shift+minus gaps inner all minus 5
+        bindsym Shift+0     gaps inner all set 0
+
+        bindsym Return mode "$wine_gaps"
+        bindsym Escape mode "default"
+}
+
+# start both polybar
+exec_always --no-startup-id $HOME/bin/run-polybar.sh
diff --git a/home-config/picom/picom.conf b/home-config/picom/picom.conf
new file mode 100644 (file)
index 0000000..75cc6b8
--- /dev/null
@@ -0,0 +1,431 @@
+#################################
+#             Shadows           #
+#################################
+
+
+# Enabled client-side shadows on windows. Note desktop windows
+# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
+# unless explicitly requested using the wintypes option.
+#
+# shadow = false
+shadow = true;
+
+# The blur radius for shadows, in pixels. (defaults to 12)
+# shadow-radius = 12
+shadow-radius = 7;
+
+# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+# shadow-opacity = .75
+
+# The left offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-x = -15
+shadow-offset-x = -7;
+
+# The top offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-y = -15
+shadow-offset-y = -7;
+
+# Red color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-red = 0
+
+# Green color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-green = 0
+
+# Blue color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-blue = 0
+
+# Hex string color value of shadow (#000000 - #FFFFFF, defaults to #000000). This option will override options set shadow-(red/green/blue)
+# shadow-color = "#000000"
+
+# Specify a list of conditions of windows that should have no shadow.
+# shadow-exclude = []
+shadow-exclude = [
+  "name = 'Notification'",
+  "class_g = 'Conky'",
+  "class_g ?= 'Notify-osd'",
+  "class_g = 'Cairo-clock'",
+  "class_g = 'steam'",
+  "class_g = 'org.nickvision.cavalier'"
+];
+
+# Specify a list of conditions of windows that should have no shadow painted over, such as a dock window.
+clip-shadow-above = [
+  "class_g = 'Conky'",
+  "class_g = 'org.nickvision.cavalier'"
+] 
+
+# Crop shadow of a window fully on a particular monitor to that monitor. This is
+# currently implemented using the X RandR extension.
+# crop-shadow-to-monitor = false
+
+
+#################################
+#           Fading              #
+#################################
+
+
+# Fade windows in/out when opening/closing and when opacity changes,
+#  unless no-fading-openclose is used.
+# fading = false
+fading = true;
+
+# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+# fade-in-step = 0.028
+fade-in-step = 0.0066;
+
+# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+# fade-out-step = 0.03
+fade-out-step = 0.01;
+
+# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+fade-delta = 1
+
+# Specify a list of conditions of windows that should not be faded.
+fade-exclude = [
+  "class_g = 'steam'"
+]
+
+# Do not fade on window open/close.
+no-fading-openclose = false
+
+# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
+# no-fading-destroyed-argb = false
+
+
+#################################
+#   Transparency / Opacity      #
+#################################
+
+
+# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
+# inactive-opacity = 1
+inactive-opacity = 0.8;
+
+# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+# frame-opacity = 1.0
+frame-opacity = 0.7;
+
+# Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows.
+# inactive-opacity-override = true
+inactive-opacity-override = false;
+
+# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
+# active-opacity = 1.0
+
+# Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+# inactive-dim = 0.0
+
+# Specify a list of conditions of windows that should never be considered focused.
+# focus-exclude = []
+#focus-exclude = [ "class_g = 'Cairo-clock'" ];
+
+# Use fixed inactive dim value, instead of adjusting according to window opacity.
+inactive-dim-fixed = 1.0
+
+# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
+# like `50:name *= "Firefox"`. picom-trans is recommended over this.
+# Note we don't make any guarantee about possible conflicts with other
+# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
+# example:
+#    opacity-rule = [ "80:class_g = 'URxvt'" ];
+#
+opacity-rule = [
+  "80:class_g *= 'pcmanfm-qt'",
+  "70:class_g *= 'org.nickvision.cavalier'",
+  "90:class_g *= 'TelegramDesktop'",
+  "100:class_g *= 'steam'"
+]
+
+
+#################################
+#           Corners             #
+#################################
+
+# Sets the radius of rounded window corners. When > 0, the compositor will
+# round the corners of windows. Does not interact well with
+# `transparent-clipping`.
+corner-radius = 0
+
+# Exclude conditions for rounded corners.
+rounded-corners-exclude = [
+  "window_type = 'dock'",
+  "window_type = 'desktop'"
+];
+
+
+#################################
+#     Background-Blurring       #
+#################################
+
+
+# Parameters for background blurring, see the *BLUR* section for more information.
+blur-method = "kernel"
+blur-size = 10
+#
+blur-deviation = 5
+#
+# blur-strength = 5
+
+# Blur background of semi-transparent / ARGB windows.
+# Bad in performance, with driver-dependent behavior.
+# The name of the switch may change without prior notifications.
+#
+# blur-background = false
+
+# Blur background of windows when the window frame is not opaque.
+# Implies:
+#    blur-background
+# Bad in performance, with driver-dependent behavior. The name may change.
+#
+# blur-background-frame = false
+
+
+# Use fixed blur strength rather than adjusting according to window opacity.
+# blur-background-fixed = false
+
+
+# Specify the blur convolution kernel, with the following format:
+# example:
+#blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
+blur-kern = "15,15,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,";
+#
+#blur-kern = ""
+#blur-kern = "3x3box";
+
+
+# Exclude conditions for background blur.
+# blur-background-exclude = []
+#blur-background-exclude = [
+#  "window_type = 'dock'",
+#  "window_type = 'desktop'"
+#];
+
+#################################
+#       General Settings        #
+#################################
+
+# Enable remote control via D-Bus. See the man page for more details.
+# dbus = true
+
+# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
+daemon = true
+
+# Specify the backend to use: `xrender`, `glx`, `egl` or `xr_glx_hybrid`.
+# `xrender` is the default one.
+#
+backend = "glx"
+#backend = "xrender";
+
+# Use higher precision during rendering, and apply dither when presenting the
+# rendered screen. Reduces banding artifacts, but might cause performance
+# degradation. Only works with OpenGL.
+dithered-present = false;
+
+# Enable/disable VSync.
+# vsync = false
+vsync = true;
+
+# Try to detect WM windows (a non-override-redirect window with no
+# child that has 'WM_STATE') and mark them as active.
+#
+# mark-wmwin-focused = false
+mark-wmwin-focused = true;
+
+# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
+# mark-ovredir-focused = false
+mark-ovredir-focused = true;
+
+# Try to detect windows with rounded corners and don't consider them
+# shaped windows. The accuracy is not very high, unfortunately.
+#
+# detect-rounded-corners = false
+detect-rounded-corners = true;
+
+# Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers
+# not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows.
+#
+# detect-client-opacity = false
+detect-client-opacity = true;
+
+# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
+# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
+# provided that the WM supports it.
+#
+# use-ewmh-active-win = false
+
+# Unredirect all windows if a full-screen opaque window is detected,
+# to maximize performance for full-screen windows. Known to cause flickering
+# when redirecting/unredirecting windows.
+#
+unredir-if-possible = true
+
+# Delay before unredirecting the window, in milliseconds. Defaults to 0.
+# unredir-if-possible-delay = 0
+
+# Conditions of windows that shouldn't be considered full-screen for unredirecting screen.
+# unredir-if-possible-exclude = []
+
+# Use 'WM_TRANSIENT_FOR' to group windows, and consider windows
+# in the same group focused at the same time.
+#
+# detect-transient = false
+detect-transient = true;
+
+# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
+# group focused at the same time. This usually means windows from the same application
+# will be considered focused or unfocused at the same time.
+# 'WM_TRANSIENT_FOR' has higher priority if detect-transient is enabled, too.
+#
+detect-client-leader = true
+
+# Resize damaged region by a specific number of pixels.
+# A positive value enlarges it while a negative one shrinks it.
+# If the value is positive, those additional pixels will not be actually painted
+# to screen, only used in blur calculation, and such. (Due to technical limitations,
+# with use-damage, those pixels will still be incorrectly painted to screen.)
+# Primarily used to fix the line corruption issues of blur,
+# in which case you should use the blur radius value here
+# (e.g. with a 3x3 kernel, you should use `--resize-damage 1`,
+# with a 5x5 one you use `--resize-damage 2`, and so on).
+# May or may not work with *--glx-no-stencil*. Shrinking doesn't function correctly.
+#
+# resize-damage = 1
+
+# Specify a list of conditions of windows that should be painted with inverted color.
+# Resource-hogging, and is not well tested.
+#
+# invert-color-include = []
+
+# GLX backend: Avoid using stencil buffer, useful if you don't have a stencil buffer.
+# Might cause incorrect opacity when rendering transparent content (but never
+# practically happened) and may not work with blur-background.
+# My tests show a 15% performance boost. Recommended.
+#
+# glx-no-stencil = false
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes,
+# but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.).
+# Recommended if it works.
+#
+# glx-no-rebind-pixmap = false
+
+# Disable the use of damage information.
+# This cause the whole screen to be redrawn every time, instead of the part of the screen
+# has actually changed. Potentially degrades the performance, but might fix some artifacts.
+# The opposing option is use-damage
+#
+# no-use-damage = false
+use-damage = true;
+
+# Use X Sync fence to sync clients' draw calls, to make sure all draw
+# calls are finished before picom starts drawing. Needed on nvidia-drivers
+# with GLX backend for some users.
+#
+xrender-sync-fence = true
+
+# GLX backend: Use specified GLSL fragment shader for rendering window
+# contents. Read the man page for a detailed explanation of the interface.
+#
+# window-shader-fg = "default"
+
+# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
+# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
+#
+# window-shader-fg-rule = [
+#   "my_shader.frag:window_type != 'dock'"
+# ]
+
+# Force all windows to be painted with blending. Useful if you
+# have a glx-fshader-win that could turn opaque pixels transparent.
+#
+# force-win-blend = false
+
+# Do not use EWMH to detect fullscreen windows.
+# Reverts to checking if a window is fullscreen based only on its size and coordinates.
+#
+# no-ewmh-fullscreen = false
+
+# Dimming bright windows so their brightness doesn't exceed this set value.
+# Brightness of a window is estimated by averaging all pixels in the window,
+# so this could comes with a performance hit.
+# Setting this to 1.0 disables this behaviour. Requires --use-damage to be disabled. (default: 1.0)
+#
+# max-brightness = 1.0
+
+# Make transparent windows clip other windows like non-transparent windows do,
+# instead of blending on top of them.
+#
+# transparent-clipping = false
+
+# Specify a list of conditions of windows that should never have transparent
+# clipping applied. Useful for screenshot tools, where you need to be able to
+# see through transparent parts of the window.
+#
+# transparent-clipping-exclude = []
+
+# Set the log level. Possible values are:
+#  "trace", "debug", "info", "warn", "error"
+# in increasing level of importance. Case doesn't matter.
+# If using the "TRACE" log level, it's better to log into a file
+# using *--log-file*, since it can generate a huge stream of logs.
+#
+# log-level = "debug"
+log-level = "warn";
+
+# Set the log file.
+# If *--log-file* is never specified, logs will be written to stderr.
+# Otherwise, logs will to written to the given file, though some of the early
+# logs might still be written to the stderr.
+# When setting this option from the config file, it is recommended to use an absolute path.
+#
+# log-file = "/path/to/your/log/file"
+
+# Show all X errors (for debugging)
+# show-all-xerrors = false
+
+# Write process ID to a file.
+# write-pid-path = "/path/to/your/log/file"
+
+# Window type settings
+#
+# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard:
+#     "unknown", "desktop", "dock", "toolbar", "menu", "utility",
+#     "splash", "dialog", "normal", "dropdown_menu", "popup_menu",
+#     "tooltip", "notification", "combo", and "dnd".
+#
+# Following per window-type options are available: ::
+#
+#   fade, shadow:::
+#     Controls window-type-specific shadow and fade settings.
+#
+#   opacity:::
+#     Controls default opacity of the window type.
+#
+#   focus:::
+#     Controls whether the window of this type is to be always considered focused.
+#     (By default, all window types except "normal" and "dialog" has this on.)
+#
+#   full-shadow:::
+#     Controls whether shadow is drawn under the parts of the window that you
+#     normally won't be able to see. Useful when the window has parts of it
+#     transparent, and you want shadows in those areas.
+#
+#   clip-shadow-above:::
+#     Controls whether shadows that would have been drawn above the window should
+#     be clipped. Useful for dock windows that should have no shadow painted on top.
+#
+#   redir-ignore:::
+#     Controls whether this type of windows should cause screen to become
+#     redirected again after been unredirected. If you have unredir-if-possible
+#     set, and doesn't want certain window to cause unnecessary screen redirection,
+#     you can set this to `true`.
+#
+wintypes:
+{
+  tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
+  dock = { shadow = false; clip-shadow-above = true; }
+  dnd = { shadow = false; }
+  popup_menu = { opacity = 0.8; }
+  dropdown_menu = { opacity = 0.8; }
+};
diff --git a/home-config/polybar/config b/home-config/polybar/config
new file mode 100644 (file)
index 0000000..e5706d8
--- /dev/null
@@ -0,0 +1,118 @@
+[colors]
+background = ${xrdb:color0:#bb000000}
+foreground = ${xrdb:color7:#222}
+foreground-alt = ${xrdb:color7:#cdcdcd}
+primary = ${xrdb:color1:#01ebea}
+secondary = ${xrdb:color2:#f57c00}
+alert = ${xrdb:color3:#f61717}
+
+[settings]
+pseudo-transparency = false
+
+
+[bar/top]
+override-redirect = true
+bottom = false
+width = 100%
+height = 28px
+underline-size = 1px
+underline-color = ${colors.primary}
+background = ${colors.background}
+foreground = ${colors.foreground}
+#separator = ·
+separator-padding = 1
+separator-foreground = ${colors.secondary}
+font-0 = "Inconsolata Nerd Font:weight=regular;2"
+padding = 1
+modules-left = slackware gmail-0 gmail-2 gmail-1 pulseaudio 
+modules-center = xworkspaces
+modules-right = info-airqualityindex weather xkeyboard
+dpi = 96
+enable-ipc = true
+
+[bar/bottom]
+override-redirect = true
+bottom = true
+width = 100%
+height = 24px
+background = ${colors.background}
+foreground = ${colors.foreground}
+#separator = ·
+#separator-padding = 1
+separator-foreground = ${colors.secondary}
+font-0 = "Inconsolata Nerd Font:weight=regular;2"
+padding = 1px
+modules-left = duckstation dunst-snooze 
+modules-center = windowlist
+modules-right = date
+dpi = 96
+enable-ipc = true
+
+# MODULES
+
+# airquality
+include-file = /home/danix/.config/polybar/modules/airquality.ini
+
+# duckstation
+include-file = /home/danix/.config/polybar/modules/duckstation.ini
+
+# i3
+#include-file = /home/danix/.config/polybar/modules/i3.ini
+
+# tray
+include-file = /home/danix/.config/polybar/modules/tray.ini
+
+# dunst-snooze
+include-file = /home/danix/.config/polybar/modules/dunst-snooze.ini
+
+# workspaces
+include-file = /home/danix/.config/polybar/modules/workspaces.ini
+
+# disks
+#include-file = /home/danix/.config/polybar/modules/filesys.ini
+
+# ram
+#include-file = /home/danix/.config/polybar/modules/memory.ini
+
+# cpu
+#include-file = /home/danix/.config/polybar/modules/cpu.ini
+
+# network
+#include-file = /home/danix/.config/polybar/modules/network.ini
+
+# slackware
+include-file = /home/danix/.config/polybar/modules/slackware.ini
+
+# kdeconnect
+# include-file = /home/danix/.config/polybar/modules/kdeconnect.ini
+
+# gmail-0
+include-file = /home/danix/.config/polybar/modules/gmail-0.ini
+
+# gmail-1
+include-file = /home/danix/.config/polybar/modules/gmail-1.ini
+
+# gmail-2
+include-file = /home/danix/.config/polybar/modules/gmail-2.ini
+
+# pulseaudio
+include-file = /home/danix/.config/polybar/modules/pulseaudio.ini
+
+# windows
+include-file = /home/danix/.config/polybar/modules/windows.ini
+
+# date
+include-file = /home/danix/.config/polybar/modules/date.ini
+
+# battery
+#include-file = /home/danix/.config/polybar/modules/battery.ini
+
+# weather
+include-file = /home/danix/.config/polybar/modules/weather.ini
+
+# keyboard
+include-file = /home/danix/.config/polybar/modules/keyboard.ini
+
+# windowlist
+include-file = /home/danix/.config/polybar/modules/windowlist.ini
+
diff --git a/home-config/polybar/config.ini-original b/home-config/polybar/config.ini-original
new file mode 100644 (file)
index 0000000..906e6cd
--- /dev/null
@@ -0,0 +1,423 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+;background = ${xrdb:color0:#222}
+background = #222
+background-alt = #444
+;foreground = ${xrdb:color7:#222}
+foreground = #dfdfdf
+foreground-alt = #555
+primary = #ffb52a
+secondary = #e60053
+alert = #bd2c40
+
+[bar/downbar]
+;monitor = ${env:MONITOR:HDMI-1}
+width = 100%
+height = 27
+;offset-x = 1%
+;offset-y = 1%
+radius = 6.0
+fixed-center = false
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3
+line-color = #f00
+
+border-size = 4
+border-color = #00000000
+
+padding-left = 0
+padding-right = 2
+
+module-margin-left = 1
+module-margin-right = 2
+
+font-0 = fixed:pixelsize=10;1
+font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
+font-2 = siji:pixelsize=10;1
+
+modules-left = bspwm i3
+modules-center = 
+modules-right = filesystem xbacklight alsa pulseaudio xkeyboard memory cpu wlan eth battery temperature date powermenu
+
+tray-position = right
+tray-padding = 2
+;tray-background = #0063ff
+
+;wm-restack = bspwm
+;wm-restack = i3
+
+;override-redirect = true
+
+;scroll-up = bspwm-desknext
+;scroll-down = bspwm-deskprev
+
+;scroll-up = i3wm-wsnext
+;scroll-down = i3wm-wsprev
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:30:...%
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-prefix-underline = ${colors.secondary}
+
+label-layout = %layout%
+label-layout-underline = ${colors.secondary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-background = ${colors.secondary}
+label-indicator-underline = ${colors.secondary}
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.foreground-alt}
+
+[module/bspwm]
+type = internal/bspwm
+
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+label-occupied = %index%
+label-occupied-padding = 2
+
+label-urgent = %index%!
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+label-empty = %index%
+label-empty-foreground = ${colors.foreground-alt}
+label-empty-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+[module/i3]
+type = internal/i3
+format = <label-state> <label-mode>
+index-sort = true
+wrapping-scroll = false
+
+; Only show workspaces on the same output as the bar
+;pin-workspaces = true
+
+label-mode-padding = 2
+label-mode-foreground = #000
+label-mode-background = ${colors.primary}
+
+; focused = Active workspace on focused monitor
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+; unfocused = Inactive workspace on any monitor
+label-unfocused = %index%
+label-unfocused-padding = 2
+
+; visible = Active workspace on unfocused monitor
+label-visible = %index%
+label-visible-background = ${self.label-focused-background}
+label-visible-underline = ${self.label-focused-underline}
+label-visible-padding = ${self.label-focused-padding}
+
+; urgent = Workspace with urgency hint set
+label-urgent = %index%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+
+[module/mpd]
+type = internal/mpd
+format-online = <label-song>  <icon-prev> <icon-stop> <toggle> <icon-next>
+
+icon-prev = 
+icon-stop = 
+icon-play = 
+icon-pause = 
+icon-next = 
+
+label-song-maxlen = 25
+label-song-ellipsis = true
+
+[module/xbacklight]
+type = internal/xbacklight
+
+format = <label> <bar>
+label = BL
+
+bar-width = 10
+bar-indicator = |
+bar-indicator-foreground = #fff
+bar-indicator-font = 2
+bar-fill = ─
+bar-fill-font = 2
+bar-fill-foreground = #9f78e1
+bar-empty = ─
+bar-empty-font = 2
+bar-empty-foreground = ${colors.foreground-alt}
+
+[module/backlight-acpi]
+inherit = module/xbacklight
+type = internal/backlight
+card = intel_backlight
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #f90000
+label = %percentage:2%%
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #4bffdc
+label = %percentage_used%%
+
+[module/wlan]
+type = internal/network
+interface = wlan0
+interval = 3.0
+
+format-connected = <ramp-signal> <label-connected>
+format-connected-underline = #9f78e1
+label-connected = %essid%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+ramp-signal-0 = 
+ramp-signal-1 = 
+ramp-signal-2 = 
+ramp-signal-3 = 
+ramp-signal-4 = 
+ramp-signal-foreground = ${colors.foreground-alt}
+
+[module/eth]
+type = internal/network
+interface = eth0
+interval = 3.0
+
+format-connected-underline = #55aa55
+format-connected-prefix = " "
+format-connected-prefix-foreground = ${colors.foreground-alt}
+label-connected = %local_ip%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+[module/date]
+type = internal/date
+interval = 5
+
+date =
+date-alt = " %Y-%m-%d"
+
+time = %H:%M
+time-alt = %H:%M:%S
+
+format-prefix = 
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #0a6cf5
+
+label = %date% %time%
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL %percentage%%
+label-volume-foreground = ${root.foreground}
+
+label-muted = 🔇 muted
+label-muted-foreground = #666
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/alsa]
+type = internal/alsa
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL
+label-volume-foreground = ${root.foreground}
+
+format-muted-prefix = " "
+format-muted-foreground = ${colors.foreground-alt}
+label-muted = sound muted
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/battery]
+type = internal/battery
+battery = BAT0
+adapter = AC0
+full-at = 98
+
+format-charging = <animation-charging> <label-charging>
+format-charging-underline = #ffb52a
+
+format-discharging = <animation-discharging> <label-discharging>
+format-discharging-underline = ${self.format-charging-underline}
+
+format-full-prefix = " "
+format-full-prefix-foreground = ${colors.foreground-alt}
+format-full-underline = ${self.format-charging-underline}
+
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-foreground = ${colors.foreground-alt}
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-foreground = ${colors.foreground-alt}
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-foreground = ${colors.foreground-alt}
+animation-discharging-framerate = 750
+
+[module/temperature]
+type = internal/temperature
+thermal-zone = 0
+warn-temperature = 60
+
+format = <ramp> <label>
+format-underline = #f50a4d
+format-warn = <ramp> <label-warn>
+format-warn-underline = ${self.format-underline}
+
+label = %temperature-c%
+label-warn = %temperature-c%
+label-warn-foreground = ${colors.secondary}
+
+ramp-0 = 
+ramp-1 = 
+ramp-2 = 
+ramp-foreground = ${colors.foreground-alt}
+
+[module/powermenu]
+type = custom/menu
+
+expand-right = true
+
+format-spacing = 1
+
+label-open = 
+label-open-foreground = ${colors.secondary}
+label-close =  cancel
+label-close-foreground = ${colors.secondary}
+label-separator = |
+label-separator-foreground = ${colors.foreground-alt}
+
+menu-0-0 = reboot
+menu-0-0-exec = menu-open-1
+menu-0-1 = power off
+menu-0-1-exec = menu-open-2
+
+menu-1-0 = cancel
+menu-1-0-exec = menu-open-0
+menu-1-1 = reboot
+menu-1-1-exec = sudo reboot
+
+menu-2-0 = power off
+menu-2-0-exec = sudo poweroff
+menu-2-1 = cancel
+menu-2-1-exec = menu-open-0
+
+[settings]
+screenchange-reload = true
+;compositing-background = xor
+;compositing-background = screen
+;compositing-foreground = source
+;compositing-border = over
+;pseudo-transparency = false
+
+[global/wm]
+margin-top = 5
+margin-bottom = 5
+
+; vim:ft=dosini
diff --git a/home-config/polybar/config.ini-vecchio b/home-config/polybar/config.ini-vecchio
new file mode 100644 (file)
index 0000000..809dac3
--- /dev/null
@@ -0,0 +1,383 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+;background = ${xrdb:color0:#222}
+background = #222
+background-alt = #444
+;foreground = ${xrdb:color7:#222}
+foreground = #dfdfdf
+foreground-alt = #555
+primary = #ffb52a
+secondary = #e60053
+alert = #bd2c40
+
+[bar/downbar]
+;monitor = ${env:MONITOR:HDMI-1}
+width = 100%
+height = 27
+;offset-x = 1%
+;offset-y = 1%
+radius = 6.0
+fixed-center = false
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3
+line-color = #f00
+
+border-size = 4
+border-color = #00000000
+
+padding-left = 0
+padding-right = 2
+
+module-margin-left = 1
+module-margin-right = 2
+
+font-0 = fixed:pixelsize=10;1
+font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
+font-2 = siji:pixelsize=10;1
+
+modules-left = i3
+modules-center = 
+modules-right = filesystem xbacklight pulseaudio xkeyboard memory cpu wlan eth battery temperature date powermenu
+
+tray-position = right
+tray-padding = 2
+;tray-background = #0063ff
+
+;wm-restack = i3
+
+;override-redirect = true
+
+scroll-up = i3wm-wsnext
+scroll-down = i3wm-wsprev
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:30:...%
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-prefix-underline = ${colors.secondary}
+
+label-layout = %layout%
+label-layout-underline = ${colors.secondary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-background = ${colors.secondary}
+label-indicator-underline = ${colors.secondary}
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.foreground-alt}
+
+[module/i3]
+type = internal/i3
+format = <label-state> <label-mode>
+index-sort = true
+wrapping-scroll = false
+
+; Only show workspaces on the same output as the bar
+;pin-workspaces = true
+
+label-mode-padding = 2
+label-mode-foreground = #000
+label-mode-background = ${colors.primary}
+
+; focused = Active workspace on focused monitor
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+; unfocused = Inactive workspace on any monitor
+label-unfocused = %index%
+label-unfocused-padding = 2
+
+; visible = Active workspace on unfocused monitor
+label-visible = %index%
+label-visible-background = ${self.label-focused-background}
+label-visible-underline = ${self.label-focused-underline}
+label-visible-padding = ${self.label-focused-padding}
+
+; urgent = Workspace with urgency hint set
+label-urgent = %index%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+[module/xbacklight]
+type = internal/xbacklight
+
+format = <label> <bar>
+label = BL
+
+bar-width = 10
+bar-indicator = |
+bar-indicator-foreground = #fff
+bar-indicator-font = 2
+bar-fill = ─
+bar-fill-font = 2
+bar-fill-foreground = #9f78e1
+bar-empty = ─
+bar-empty-font = 2
+bar-empty-foreground = ${colors.foreground-alt}
+
+[module/backlight-acpi]
+inherit = module/xbacklight
+type = internal/backlight
+card = intel_backlight
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #f90000
+label = %percentage:2%%
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #4bffdc
+label = %percentage_used%%
+
+[module/wlan]
+type = internal/network
+interface = wlan0
+interval = 3.0
+
+format-connected = <ramp-signal> <label-connected>
+format-connected-underline = #9f78e1
+label-connected = %essid%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+ramp-signal-0 = 
+ramp-signal-1 = 
+ramp-signal-2 = 
+ramp-signal-3 = 
+ramp-signal-4 = 
+ramp-signal-foreground = ${colors.foreground-alt}
+
+[module/eth]
+type = internal/network
+interface = eth0
+interval = 3.0
+
+format-connected-underline = #55aa55
+format-connected-prefix = " "
+format-connected-prefix-foreground = ${colors.foreground-alt}
+label-connected = %local_ip%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+[module/date]
+type = internal/date
+interval = 5
+
+date =
+date-alt = " %Y-%m-%d"
+
+time = %H:%M
+time-alt = %H:%M:%S
+
+format-prefix = 
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #0a6cf5
+
+label = %date% %time%
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL %percentage%%
+label-volume-foreground = ${root.foreground}
+
+label-muted = 🔇 muted
+label-muted-foreground = #666
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/alsa]
+type = internal/alsa
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL
+label-volume-foreground = ${root.foreground}
+
+format-muted-prefix = " "
+format-muted-foreground = ${colors.foreground-alt}
+label-muted = sound muted
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/battery]
+type = internal/battery
+battery = BAT0
+adapter = AC0
+full-at = 98
+
+format-charging = <animation-charging> <label-charging>
+format-charging-underline = #ffb52a
+
+format-discharging = <animation-discharging> <label-discharging>
+format-discharging-underline = ${self.format-charging-underline}
+
+format-full-prefix = " "
+format-full-prefix-foreground = ${colors.foreground-alt}
+format-full-underline = ${self.format-charging-underline}
+
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-foreground = ${colors.foreground-alt}
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-foreground = ${colors.foreground-alt}
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-foreground = ${colors.foreground-alt}
+animation-discharging-framerate = 750
+
+[module/temperature]
+type = internal/temperature
+thermal-zone = 0
+warn-temperature = 60
+
+format = <ramp> <label>
+format-underline = #f50a4d
+format-warn = <ramp> <label-warn>
+format-warn-underline = ${self.format-underline}
+
+label = %temperature-c%
+label-warn = %temperature-c%
+label-warn-foreground = ${colors.secondary}
+
+ramp-0 = 
+ramp-1 = 
+ramp-2 = 
+ramp-foreground = ${colors.foreground-alt}
+
+[module/powermenu]
+type = custom/menu
+
+expand-right = true
+
+format-spacing = 1
+
+label-open = 
+label-open-foreground = ${colors.secondary}
+label-close =  cancel
+label-close-foreground = ${colors.secondary}
+label-separator = |
+label-separator-foreground = ${colors.foreground-alt}
+
+menu-0-0 = reboot
+menu-0-0-exec = menu-open-1
+menu-0-1 = power off
+menu-0-1-exec = menu-open-2
+
+menu-1-0 = cancel
+menu-1-0-exec = menu-open-0
+menu-1-1 = reboot
+menu-1-1-exec = sudo reboot
+
+menu-2-0 = power off
+menu-2-0-exec = sudo poweroff
+menu-2-1 = cancel
+menu-2-1-exec = menu-open-0
+
+[settings]
+screenchange-reload = true
+;compositing-background = xor
+;compositing-background = screen
+;compositing-foreground = source
+;compositing-border = over
+;pseudo-transparency = false
+
+[global/wm]
+margin-top = 5
+margin-bottom = 5
+
+; vim:ft=dosini
diff --git a/home-config/polybar/config.ini.boh b/home-config/polybar/config.ini.boh
new file mode 100644 (file)
index 0000000..f6f6f74
--- /dev/null
@@ -0,0 +1,333 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+background = #00000000
+background-alt = #373B41
+foreground = #000319
+primary = #01ebea
+secondary = #0168eb
+alert = #f61717
+full = #01eb1c
+disabled = #c40082
+
+[bar/main]
+width = 100%
+height = 22pt
+radius = 0
+
+; dpi = 96
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 2pt
+
+;border-left-size = 20
+;border-right-size = 20
+;border-top-size = 7
+;border-color = #00000000
+
+padding-left = 0
+padding-right = 1
+
+module-margin = 1
+
+separator = |
+separator-foreground = ${colors.foreground}
+
+font-0 = "JetBrainsMonoNL Nerd Font:weight=bold;2"
+
+modules-left = xworkspaces
+modules-center = memory date cpu
+modules-right = pulseaudio-control-output wlan powerbutton logoutbutton battery
+# modules-disabled = filesystem xkeyboard xwindow eth pulseaudio 
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+enable-ipc = true
+
+
+[module/xworkspaces]
+type = internal/xworkspaces
+
+label-active = %name%
+label-active-background = ${colors.background}
+label-active-underline= ${colors.foreground}
+label-active-padding = 1
+
+label-occupied = %name%
+label-occupied-padding = 1
+
+label-urgent = %name%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 1
+
+label-empty = %name%
+label-empty-foreground = ${colors.disabled}
+label-empty-padding = 1
+
+#[module/xwindow]
+#type = internal/xwindow
+#label = %title:0:60:...%
+
+;[module/filesystem]
+;type = internal/fs
+;interval = 25
+
+;mount-0 = /
+
+;label-mounted = %{F#F0C674}%mountpoint%%{F-} %percentage_used%%
+
+;label-unmounted = %mountpoint% not mounted
+;label-unmounted-foreground = ${colors.disabled}
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume-prefix = "墳 "
+format-volume-prefix-foreground = ${colors.primary}
+format-volume = <label-volume>
+
+label-volume = %percentage%%
+
+label-muted = 婢 MUTED 
+label-muted-foreground = ${colors.disabled}
+
+#[module/xkeyboard]
+#type = internal/xkeyboard
+#blacklist-0 = num lock
+
+#label-layout = %layout%
+#label-layout-foreground = ${colors.primary}
+
+#label-indicator-padding = 2
+#label-indicator-margin = 1
+#label-indicator-foreground = ${colors.background}
+#label-indicator-background = ${colors.secondary}
+
+[module/pulseaudio-control-output]
+type = custom/script
+tail = true
+label-padding = 0
+label-foreground = ${colors.foreground}
+
+# Icons mixed from Font Awesome 5 and Material Icons
+# You can copy-paste your options for each possible action, which is more
+# trouble-free but repetitive, or apply only the relevant ones (for example
+# --node-blacklist is only needed for next-node).
+exec = pulseaudio-control --icons-volume " , " --icon-muted " " --node-nicknames-from "device.description" --node-nickname "alsa_output.pci-0000_00_1b.0.analog-stereo: " --node-nickname "alsa_output.usb-Kingston_HyperX_Virtual_Surround_Sound_00000000-00.analog-stereo: " listen
+click-right = exec pavucontrol &
+click-left = pulseaudio-control togmute
+click-middle = pulseaudio-control --node-blacklist "alsa_output.pci-0000_01_00.1.hdmi-stereo-extra2" next-node
+scroll-up = pulseaudio-control --volume-max 130 up
+scroll-down = pulseaudio-control --volume-max 130 down
+
+[module/pulseaudio-control-input]
+type = custom/script
+tail = true
+format-underline = ${colors.primary}
+label-padding = 2
+label-foreground = ${colors.primary}
+
+# Use --node-blacklist to remove the unwanted PulseAudio .monitor that are child of sinks
+exec = pulseaudio-control  --node-type input --icons-volume "" --icon-muted "" --node-nickname "alsa_output.pci-0000_0c_00.3.analog-stereo:  Webcam" --node-nickname "alsa_output.usb-Kingston_HyperX_Virtual_Surround_Sound_00000000-00.analog-stereo: Headphones" --node-blacklist "*.monitor" listen
+click-right = exec pavucontrol &
+click-left = pulseaudio-control --node-type input togmute
+click-middle = pulseaudio-control --node-type input next-node
+scroll-up = pulseaudio-control --node-type input --volume-max 130 up
+scroll-down = pulseaudio-control --node-type input --volume-max 130 down
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " " 
+format-prefix-foreground = ${colors.foreground}
+label = %percentage_used:2%%
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = "﬙ "
+format-prefix-foreground = ${colors.foreground}
+label = %percentage:2%%
+
+[network-base]
+type = internal/network
+interval = 5
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+label-disconnected = %{F#F0C674}%ifname%%{F#707880} disconnected
+
+[module/wlan]
+inherit = network-base
+interface-type = wireless
+label-connected = %{A1:nm-connection-editor:} %{A} 
+label-connected-foreground = ${colors.foreground}
+
+#[module/eth]
+#inherit = network-base
+#interface-type = wired
+#label-connected = %{F#F0C674}%ifname%%{F-} %local_ip%
+
+[module/powerbutton]
+type = custom/script
+exec = echo " "
+click-left = poweroff
+
+[module/logoutbutton]
+type = custom/script
+exec = echo "⏼ "
+click-left = i3-msg exit
+
+[module/date]
+type = internal/date
+interval = 1
+
+date =  %H:%M:%S
+date-alt =  %d|%m|%y
+
+label = %date%
+label-foreground = ${colors.foreground}
+
+[module/cava]
+type = custom/script
+tail = true
+exec = $HOME/.config/polybar/cava.sh
+format = <label>
+format-font = 5
+label = %output%
+label-foreground = #01ebea
+
+ [module/battery]
+type = internal/battery
+
+full-at = 100
+low-at = 15
+
+; Use the following command to list batteries and adapters:
+; $ ls -1 /sys/class/power_supply/
+battery = BAT0
+adapter = AC
+
+; Disable polling by setting the interval to 0.
+;
+; Default: 5
+poll-interval = 5
+
+; Available tags:
+;   <label-charging> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+;   <animation-charging>
+format-charging = <animation-charging>  <label-charging> 
+format-charging-foreground = ${colors.foreground}
+
+; Available tags:
+;   <label-discharging> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+;   <animation-discharging>
+format-discharging = <animation-discharging>  <label-discharging>
+format-discharging-foreground = ${colors.foreground}
+
+; Available tags:
+;   <label-full> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+format-full = <animation-charging>  <label-full>
+format-full-foreground = ${colors.foreground}
+
+; Format used when battery level drops to low-at
+; If not defined, format-discharging is used instead.
+; Available tags:
+;   <label-low>
+;   <animation-low>
+;   <bar-capacity>
+;   <ramp-capacity>
+; New in version 3.6.0
+format-low = <animation-discharging>  <label-low>
+format-low-foreground = ${colors.foreground}
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current charge rate in watts)
+label-charging = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current discharge rate in watts)
+label-discharging = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+label-full = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current discharge rate in watts)
+; New in version 3.6.0
+label-low = %percentage%%
+
+; Only applies if <ramp-capacity> is used
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-3 = 
+ramp-capacity-4 = 
+
+; Only applies if <bar-capacity> is used
+bar-capacity-width = 10
+
+; Only applies if <animation-charging> is used
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-3 = 
+animation-charging-4 = 
+; Framerate in milliseconds
+animation-charging-framerate = 750
+
+; Only applies if <animation-discharging> is used
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-3 = 
+animation-discharging-4 = 
+; Framerate in milliseconds
+animation-discharging-framerate = 500
+
+; Only applies if <animation-low> is used
+; New in version 3.6.0
+animation-low-0 = !
+animation-low-1 = 
+animation-low-framerate = 200
+
+[settings]
+screenchange-reload = true
+pseudo-transparency = true
+
+; vim:ft=dosini
diff --git a/home-config/polybar/modules/airquality.ini b/home-config/polybar/modules/airquality.ini
new file mode 100644 (file)
index 0000000..52c0962
--- /dev/null
@@ -0,0 +1,5 @@
+[module/info-airqualityindex]
+type = custom/script
+exec = ~/bin/info-airqualityindex.sh
+interval = 600
+
diff --git a/home-config/polybar/modules/battery.ini b/home-config/polybar/modules/battery.ini
new file mode 100644 (file)
index 0000000..33b7882
--- /dev/null
@@ -0,0 +1,39 @@
+[module/battery]
+type = internal/battery
+
+battery = BAT0
+adapter = AC0
+
+full-at = 95
+low-at = 15
+poll-interval = 5
+format-charging = <animation-charging>  <label-charging> 
+format-charging-foreground = ${colors.foreground}
+
+format-discharging = <animation-discharging>  <label-discharging>
+format-discharging-foreground = ${colors.foreground}
+
+format-full = <label-full>
+format-full-foreground = ${colors.full}
+
+format-low = <label-low>
+format-low-foreground = ${colors.alert}
+
+label-charging =  %percentage%%
+label-discharging = %percentage%%
+label-full =   %percentage%%
+label-low =   %percentage%%
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-3 = 
+animation-charging-4 = 
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-3 = 
+animation-discharging-4 = 
+animation-discharging-framerate = 750
diff --git a/home-config/polybar/modules/cpu.ini b/home-config/polybar/modules/cpu.ini
new file mode 100644 (file)
index 0000000..a459215
--- /dev/null
@@ -0,0 +1,15 @@
+[module/cpu]
+type = internal/cpu
+interval = 0.5
+warn-percentage = 95
+format = <label> <ramp-coreload>
+label = 󰻠 %percentage%%
+ramp-coreload-spacing = 1px
+ramp-coreload-0 = ▁
+ramp-coreload-1 = ▂
+ramp-coreload-2 = ▃
+ramp-coreload-3 = ▄
+ramp-coreload-4 = ▅
+ramp-coreload-5 = ▆
+ramp-coreload-6 = ▇
+ramp-coreload-7 = █
diff --git a/home-config/polybar/modules/date.ini b/home-config/polybar/modules/date.ini
new file mode 100644 (file)
index 0000000..528a44d
--- /dev/null
@@ -0,0 +1,8 @@
+[module/date]
+type = internal/date
+
+interval = 1
+date =   %H:%M:%S
+date-alt =  %d/%m/%Y
+label = %{A3:qarma --calendar:} %date% %{A}
+label-foreground = ${colors.foreground}
diff --git a/home-config/polybar/modules/duckstation.ini b/home-config/polybar/modules/duckstation.ini
new file mode 100644 (file)
index 0000000..1e805f7
--- /dev/null
@@ -0,0 +1,10 @@
+[module/duckstation]
+type = custom/text
+click-left = /usr/bin/duckstation-qt
+
+format = <label>
+format-foreground = ${colors.foreground}
+format-padding = 2
+
+label = ""
+
diff --git a/home-config/polybar/modules/dunst-snooze.ini b/home-config/polybar/modules/dunst-snooze.ini
new file mode 100644 (file)
index 0000000..01567d2
--- /dev/null
@@ -0,0 +1,6 @@
+[module/dunst-snooze]
+type = custom/script
+exec = ~/bin/dunst-snooze.sh
+interval = 2
+click-left = ~/bin/dunst-snooze.sh --toggle &
+
diff --git a/home-config/polybar/modules/filesys.ini b/home-config/polybar/modules/filesys.ini
new file mode 100644 (file)
index 0000000..073e76e
--- /dev/null
@@ -0,0 +1,18 @@
+[fs-base]
+type = internal/fs
+fixed-values = true
+
+[module/fs-root]
+inherit = fs-base
+mount-0 = /
+label-mounted =  /: %percentage_used%%
+
+[module/fs-home]
+inherit = fs-base
+mount-0 = /home
+label-mounted =  /~: %percentage_used%%
+
+[module/fs-data]
+inherit = fs-base
+mount-0 = /data
+label-mounted =  /data: %percentage_used%%
diff --git a/home-config/polybar/modules/gmail-0.ini b/home-config/polybar/modules/gmail-0.ini
new file mode 100644 (file)
index 0000000..20d393e
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-0]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c f61717 -cr ~/.config/polybar/modules/gmail/credentials_danixland.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/0/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/home-config/polybar/modules/gmail-1.ini b/home-config/polybar/modules/gmail-1.ini
new file mode 100644 (file)
index 0000000..a0e68e1
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-1]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c 01eb1c -cr ~/.config/polybar/modules/gmail/credentials_itdm.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/2/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/home-config/polybar/modules/gmail-2.ini b/home-config/polybar/modules/gmail-2.ini
new file mode 100644 (file)
index 0000000..0099eab
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-2]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c 0111eb -cr ~/.config/polybar/modules/gmail/credentials_65d85.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/1/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/home-config/polybar/modules/gmail/.gitignore b/home-config/polybar/modules/gmail/.gitignore
new file mode 100644 (file)
index 0000000..83f6e39
--- /dev/null
@@ -0,0 +1 @@
+credentials.json
diff --git a/home-config/polybar/modules/gmail/LICENSE b/home-config/polybar/modules/gmail/LICENSE
new file mode 100644 (file)
index 0000000..ef9d518
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2022 Vyacheslav Konovalov https://github.com/crabvk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/home-config/polybar/modules/gmail/README.md b/home-config/polybar/modules/gmail/README.md
new file mode 100644 (file)
index 0000000..dc4a191
--- /dev/null
@@ -0,0 +1,67 @@
+# Polybar Gmail
+
+A [Polybar](https://github.com/jaagr/polybar) module to show unread messages from Gmail.
+
+![preview](https://github.com/crabvk/polybar-gmail/raw/master/preview.png)
+
+## Dependencies
+
+```sh
+pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
+# or use poetry
+```
+
+**Font Awesome** - default email icon
+
+**canberra-gtk-play** - new email sound notification
+
+You can change the icon or turn off sound, for more info see [script arguments](#script-arguments)
+
+## Installation
+
+```sh
+cd ~/.config/polybar
+curl -LO https://github.com/crabvk/polybar-gmail/archive/master.tar.gz
+tar zxf master.tar.gz && rm master.tar.gz
+mv polybar-gmail-master gmail
+```
+
+and obtain/refresh credentials
+
+```sh
+~/.config/polybar/gmail/auth.py
+```
+
+### Module
+
+```ini
+[module/gmail]
+type = custom/script
+exec = ~/.config/polybar/gmail/launch.py
+tail = true
+click-left = xdg-open https://mail.google.com
+```
+
+## Script arguments
+
+`-l` or `--label` - set user's mailbox [label](https://developers.google.com/gmail/api/v1/reference/users/labels/list), default: INBOX
+
+`-p` or `--prefix` - set email icon, default: 
+
+`-c` or `--color` - set new email icon color, default: #e06c75
+
+`-ns` or `--nosound` - turn off new email sound
+
+`-cr` or `--credentials` - path to your `credentials.json`, defaults to `credentials.json`
+
+### Example
+
+```sh
+./launch.py --label 'CATEGORY_PERSONAL' --prefix '✉' --color '#be5046' --nosound
+```
+
+## Get list of all your mailbox labels
+
+```python
+./list_labels.py
+```
diff --git a/home-config/polybar/modules/gmail/auth.py b/home-config/polybar/modules/gmail/auth.py
new file mode 100755 (executable)
index 0000000..2923b23
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from pathlib import Path
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from google.auth.transport.requests import Request
+
+SCOPES = 'https://www.googleapis.com/auth/gmail.labels'
+DIR = Path(__file__).resolve().parent
+CLIENT_SECRETS_PATH = Path(DIR, 'client_secrets.json')
+CREDENTIALS_PATH = Path(DIR, 'credentials.json')
+
+if Path(CREDENTIALS_PATH).is_file():
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+    if creds.expired and creds.refresh_token:
+        creds.refresh(Request())
+    else:
+        print('Credentials look OK, try to remove credentials.json if something doesn\'t work')
+        exit()
+else:
+    flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_PATH, scopes=SCOPES)
+    creds = flow.run_local_server(open_browser=False)
+
+# Save credentials
+with open(CREDENTIALS_PATH, 'w') as creds_file:
+    creds_file.write(creds.to_json())
+print('Credentials saved successfully')
diff --git a/home-config/polybar/modules/gmail/client_secrets.json b/home-config/polybar/modules/gmail/client_secrets.json
new file mode 100644 (file)
index 0000000..460b0e7
--- /dev/null
@@ -0,0 +1 @@
+{"installed":{"client_id":"1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com","project_id":"polybar-gmail","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"-aZZAslLp6ydldCAFvH9AEwi","redirect_uris":["http://localhost"]}}
\ No newline at end of file
diff --git a/home-config/polybar/modules/gmail/credentials_65d85.json b/home-config/polybar/modules/gmail/credentials_65d85.json
new file mode 100644 (file)
index 0000000..f39e67c
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVsp5UtITw6_bgZG5G0YB4yFf8ko2V-vc4Js2tP8wfuTChmkomY_AB0nmGNlsYHkRkYPiFFI-_PQtMZb0rxExdwCZ1y8UPWd85LN8pB4t0r2eMSTyGMpsXcnemmRhbtvp9OtSzQcB1eV3u91Vsb4NjpKlaCgYKAW0SARASFQGbdwaIuHXcRdBYC8Q2Bm5Bj8OzWA0163", "refresh_token": "1//09NrIslEcmjmBCgYIARAAGAkSNwF-L9IrDYd9hkTTPBNdHzP4ORlqCis5okzJHjuYqXZMVtOZArRlIESS41FLAFOPu9xbjlNpkNA", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:34:08.969897Z"}
\ No newline at end of file
diff --git a/home-config/polybar/modules/gmail/credentials_danixland.json b/home-config/polybar/modules/gmail/credentials_danixland.json
new file mode 100644 (file)
index 0000000..69300a4
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVsp32kYJwSDjvLnTP6XTPJjbPwVpdSqxseKZ093yBCtyrrcxlh-0YTU-FDrAA2xPj6qTPCLPyYW-mBXzGzSNF6h6Cjycbn4r5BV_cEYqiDtqp1TQnMrShQYSIsM2Mh405hjRfxoi48auwk9RWQJeFjfvaCgYKAXISARESFQGbdwaINWUUHQ96gBXo8jQhSFTQUw0163", "refresh_token": "1//09rAkNYR3IAN7CgYIARAAGAkSNwF-L9IrwgIPuApwQp3-s1sd_LjpWvNZN_6J6UcK8yj3mx7mXvt3n1evnLgGYOy8aF7AbRPwJqI", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:18:57.969451Z"}
\ No newline at end of file
diff --git a/home-config/polybar/modules/gmail/credentials_itdm.json b/home-config/polybar/modules/gmail/credentials_itdm.json
new file mode 100644 (file)
index 0000000..73ffee9
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVspE_K6QiraMg24MrOJvWIUnXtPjGQOoqTl8GrVd2gmdEMPpK55gJKQ_6OhZwx_eBI_P8YEv6F6aXLD8w5AGd-OY7lclbGOYp__ig9zEQJQzEcGvl0v9kVWyMnbmuwemry51NTY1aJU65rOm3I5FCrO1aCgYKAa4SARASFQGbdwaIfewk3tYDvSp9Eofoz03UhQ0163", "refresh_token": "1//09W5c9oMuaMS-CgYIARAAGAkSNwF-L9IrNiQdC25DOhbiyxvxJkK5SdjpgYq_hmSBUhlInBNGJ11d99NW5p3HG9E_m5F5SAN293I", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:32:27.200554Z"}
\ No newline at end of file
diff --git a/home-config/polybar/modules/gmail/launch.py b/home-config/polybar/modules/gmail/launch.py
new file mode 100755 (executable)
index 0000000..99f85a7
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+import time
+import argparse
+import subprocess
+from pathlib import Path
+from googleapiclient import discovery, errors
+from google.oauth2.credentials import Credentials
+from google.auth.exceptions import TransportError
+from httplib2 import ServerNotFoundError
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-l', '--label', default='INBOX')
+parser.add_argument('-p', '--prefix', default='\uf0e0')
+parser.add_argument('-c', '--color', default='#e06c75')
+parser.add_argument('-ns', '--nosound', action='store_true')
+parser.add_argument('-cr', '--credentials', default='credentials.json')
+args = parser.parse_args()
+
+DIR = Path(__file__).resolve().parent
+CREDENTIALS_PATH = Path(DIR, args.credentials)
+
+unread_prefix = '%{F' + args.color + '}' + args.prefix + ' %{F-}'
+error_prefix = '%{F' + args.color + '}\uf06a %{F-}'
+count_was = 0
+
+
+def print_count(count, is_odd=False):
+    tilde = '~' if is_odd else ''
+    output = ''
+    if count > 0:
+        output = unread_prefix + tilde + str(count)
+    else:
+        output = (args.prefix + ' ' + tilde).strip()
+    print(output, flush=True)
+
+
+def update_count(count_was):
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+    gmail = discovery.build('gmail', 'v1', credentials=creds)
+    labels = gmail.users().labels().get(userId='me', id=args.label).execute()
+    count = labels['messagesUnread']
+    print_count(count)
+    if not args.nosound and count_was < count and count > 0:
+        subprocess.run(['canberra-gtk-play', '-i', 'message'])
+    return count
+
+
+print_count(0, True)
+
+while True:
+    try:
+        if Path(CREDENTIALS_PATH).is_file():
+            count_was = update_count(count_was)
+            time.sleep(10)
+        else:
+            print(error_prefix + 'credentials not found', flush=True)
+            time.sleep(2)
+    except errors.HttpError as error:
+        if error.resp.status == 404:
+            print(error_prefix + f'"{args.label}" label not found', flush=True)
+        else:
+            print_count(count_was, True)
+        time.sleep(5)
+    except (ServerNotFoundError, OSError, TransportError):
+        print_count(count_was, True)
+        time.sleep(5)
diff --git a/home-config/polybar/modules/gmail/list_labels.py b/home-config/polybar/modules/gmail/list_labels.py
new file mode 100755 (executable)
index 0000000..4118623
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+import os
+import sys
+from googleapiclient.discovery import build
+from google.oauth2.credentials import Credentials
+
+DIR = os.path.dirname(os.path.realpath(__file__))
+CREDENTIALS_PATH = os.path.join(DIR, 'credentials.json')
+
+try:
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+except FileNotFoundError:
+    sys.exit('File ' + CREDENTIALS_PATH + ' not found. Run auth.py to obtain credentials.')
+gmail = build('gmail', 'v1', credentials=creds)
+labels = gmail.users().labels().list(userId='me').execute()
+for label in labels['labels']:
+    print(label['id'])
diff --git a/home-config/polybar/modules/gmail/poetry.lock b/home-config/polybar/modules/gmail/poetry.lock
new file mode 100644 (file)
index 0000000..f9ba2d3
--- /dev/null
@@ -0,0 +1,412 @@
+[[package]]
+name = "autopep8"
+version = "2.0.0"
+description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycodestyle = ">=2.9.1"
+tomli = "*"
+
+[[package]]
+name = "cachetools"
+version = "5.2.0"
+description = "Extensible memoizing collections and decorators"
+category = "main"
+optional = false
+python-versions = "~=3.7"
+
+[[package]]
+name = "certifi"
+version = "2022.9.24"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
+[[package]]
+name = "google-api-core"
+version = "2.10.2"
+description = "Google API client core library"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+google-auth = ">=1.25.0,<3.0dev"
+googleapis-common-protos = ">=1.56.2,<2.0dev"
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+requests = ">=2.18.0,<3.0.0dev"
+
+[package.extras]
+grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"]
+grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
+grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
+
+[[package]]
+name = "google-api-python-client"
+version = "2.66.0"
+description = "Google API Client Library for Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev"
+google-auth = ">=1.19.0,<3.0.0dev"
+google-auth-httplib2 = ">=0.1.0"
+httplib2 = ">=0.15.0,<1dev"
+uritemplate = ">=3.0.1,<5"
+
+[[package]]
+name = "google-auth"
+version = "2.14.1"
+description = "Google Authentication Library"
+category = "main"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
+
+[package.dependencies]
+cachetools = ">=2.0.0,<6.0"
+pyasn1-modules = ">=0.2.1"
+rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""}
+six = ">=1.9.0"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"]
+enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"]
+pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"]
+reauth = ["pyu2f (>=0.1.5)"]
+
+[[package]]
+name = "google-auth-httplib2"
+version = "0.1.0"
+description = "Google Authentication Library: httplib2 transport"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+google-auth = "*"
+httplib2 = ">=0.15.0"
+six = "*"
+
+[[package]]
+name = "google-auth-oauthlib"
+version = "0.7.1"
+description = "Google Authentication Library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+google-auth = ">=2.14.0"
+requests-oauthlib = ">=0.7.0"
+
+[package.extras]
+tool = ["click (>=6.0.0)"]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.57.0"
+description = "Common protobufs used in Google APIs"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+
+[package.extras]
+grpc = ["grpcio (>=1.44.0,<2.0.0dev)"]
+
+[[package]]
+name = "httplib2"
+version = "0.21.0"
+description = "A comprehensive HTTP client library."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "oauthlib"
+version = "3.2.2"
+description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+rsa = ["cryptography (>=3.0.0)"]
+signals = ["blinker (>=1.4.0)"]
+signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
+
+[[package]]
+name = "protobuf"
+version = "4.21.9"
+description = ""
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pyasn1"
+version = "0.4.8"
+description = "ASN.1 types and codecs"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.2.8"
+description = "A collection of ASN.1-based protocols modules."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pyasn1 = ">=0.4.6,<0.5.0"
+
+[[package]]
+name = "pycodestyle"
+version = "2.9.1"
+description = "Python style guide checker"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "main"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<3"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requests-oauthlib"
+version = "1.3.1"
+description = "OAuthlib authentication support for Requests."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+oauthlib = ">=3.0.0"
+requests = ">=2.0.0"
+
+[package.extras]
+rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
+
+[[package]]
+name = "rsa"
+version = "4.9"
+description = "Pure-Python RSA implementation"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4"
+
+[package.dependencies]
+pyasn1 = ">=0.1.3"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "uritemplate"
+version = "4.1.1"
+description = "Implementation of RFC 6570 URI Templates"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.10.7"
+content-hash = "ce33e021036b36c94cf6045952262c7f1d9d7287c4d3778ac9bf211b90ad2e2e"
+
+[metadata.files]
+autopep8 = [
+    {file = "autopep8-2.0.0-py2.py3-none-any.whl", hash = "sha256:ad924b42c2e27a1ac58e432166cc4588f5b80747de02d0d35b1ecbd3e7d57207"},
+    {file = "autopep8-2.0.0.tar.gz", hash = "sha256:8b1659c7f003e693199f52caffdc06585bb0716900bbc6a7442fd931d658c077"},
+]
+cachetools = [
+    {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
+    {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
+]
+certifi = [
+    {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+    {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+    {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
+google-api-core = [
+    {file = "google-api-core-2.10.2.tar.gz", hash = "sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320"},
+    {file = "google_api_core-2.10.2-py3-none-any.whl", hash = "sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e"},
+]
+google-api-python-client = [
+    {file = "google-api-python-client-2.66.0.tar.gz", hash = "sha256:4cfaf0205aa7c538c8fb1772368be3d049dfed7886adf48597e9a766e9828a6e"},
+    {file = "google_api_python_client-2.66.0-py2.py3-none-any.whl", hash = "sha256:3b45110b638232959f75418231dfb487228102a4a91a7a3e64147684befaebee"},
+]
+google-auth = [
+    {file = "google-auth-2.14.1.tar.gz", hash = "sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d"},
+    {file = "google_auth-2.14.1-py2.py3-none-any.whl", hash = "sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016"},
+]
+google-auth-httplib2 = [
+    {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"},
+    {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
+]
+google-auth-oauthlib = [
+    {file = "google-auth-oauthlib-0.7.1.tar.gz", hash = "sha256:9940f543f77d1447432a93781d7c931fb53e418023351ad4bf9e92837a1154ec"},
+    {file = "google_auth_oauthlib-0.7.1-py2.py3-none-any.whl", hash = "sha256:860e54c4b58b2664116c9cb44325bc0ec92bcd93e8211698ceea911b1b873b86"},
+]
+googleapis-common-protos = [
+    {file = "googleapis-common-protos-1.57.0.tar.gz", hash = "sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46"},
+    {file = "googleapis_common_protos-1.57.0-py2.py3-none-any.whl", hash = "sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c"},
+]
+httplib2 = [
+    {file = "httplib2-0.21.0-py3-none-any.whl", hash = "sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01"},
+    {file = "httplib2-0.21.0.tar.gz", hash = "sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34"},
+]
+idna = [
+    {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+    {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+oauthlib = [
+    {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
+    {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
+]
+protobuf = [
+    {file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"},
+    {file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"},
+    {file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"},
+    {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"},
+    {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"},
+    {file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"},
+    {file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"},
+    {file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"},
+    {file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"},
+    {file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"},
+    {file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"},
+    {file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"},
+    {file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"},
+    {file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"},
+]
+pyasn1 = [
+    {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
+    {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
+]
+pyasn1-modules = [
+    {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
+    {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
+    {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
+]
+pyparsing = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+requests = [
+    {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+    {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+]
+requests-oauthlib = [
+    {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
+    {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
+]
+rsa = [
+    {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
+    {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
+]
+six = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+tomli = [
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+uritemplate = [
+    {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
+    {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+    {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+]
diff --git a/home-config/polybar/modules/gmail/preview.png b/home-config/polybar/modules/gmail/preview.png
new file mode 100644 (file)
index 0000000..57062f2
Binary files /dev/null and b/home-config/polybar/modules/gmail/preview.png differ
diff --git a/home-config/polybar/modules/gmail/pyproject.toml b/home-config/polybar/modules/gmail/pyproject.toml
new file mode 100644 (file)
index 0000000..ff15d27
--- /dev/null
@@ -0,0 +1,22 @@
+[tool.poetry]
+name = "polybar-gmail"
+version = "0.2.1"
+description = "Polybar module to show unread messages from Gmail"
+authors = ["Vyacheslav Konovalov <crabvk@protonmail.com>"]
+license = "MIT"
+
+[tool.poetry.dependencies]
+python = "^3.10.7"
+google-api-python-client = "^2.66.0"
+google-auth-httplib2 = "^0.1.0"
+google-auth-oauthlib = "^0.7.1"
+autopep8 = "^2.0.0"
+
+[tool.poetry.dev-dependencies]
+
+[build-system]
+requires = ["poetry>=1.1.0"]
+build-backend = "poetry.masonry.api"
+
+[tool.autopep8]
+max_line_length = 100
diff --git a/home-config/polybar/modules/i3.ini b/home-config/polybar/modules/i3.ini
new file mode 100644 (file)
index 0000000..175ab6b
--- /dev/null
@@ -0,0 +1,34 @@
+[module/i3]
+type = internal/i3
+pin-workspaces = true
+show-urgent = true
+strip-wsnumbers = true
+enable-click = true
+enable-scroll = true
+wrapping-scroll = true
+
+ws-icon-0 = web;󰖟
+ws-icon-1 = editor;
+ws-icon-2 = console;󰆍
+ws-icon-3 = ssh;󰢩
+ws-icon-4 = graphic;󰽉
+ws-icon-5 = editor2;󰼭
+ws-icon-6 = chat;󰭻
+ws-icon-7 = music;
+ws-icon-default = 
+
+label-focused = %icon%
+label-focused-padding = 1
+label-focused-underline = ${colors.secondary}
+
+label-unfocused = %icon%
+
+label-visible = %icon%
+label-visible-underline = ${colors.secondary}
+
+label-urgent = %icon%
+label-urgent-underline = ${colors.alert}
+
+label-separator = |
+label-separator-padding = 1
+label-separator-foreground = ${colors.foreground}
diff --git a/home-config/polybar/modules/kdeconnect.ini b/home-config/polybar/modules/kdeconnect.ini
new file mode 100644 (file)
index 0000000..3691740
--- /dev/null
@@ -0,0 +1,5 @@
+[module/kdeconnect]  
+type = custom/script  
+exec = polybar-kdeconnect.sh -d
+tail = true
+interval = 600
diff --git a/home-config/polybar/modules/keyboard.ini b/home-config/polybar/modules/keyboard.ini
new file mode 100644 (file)
index 0000000..51fd4ac
--- /dev/null
@@ -0,0 +1,20 @@
+[module/xkeyboard]
+type = internal/xkeyboard
+#xkeyboard.switch
+format-spacing = 1
+label-layout = %layout%
+label-layout-foreground = ${colors.foreground}
+label-indicator-padding = 1
+label-indicator-margin = 0
+label-indicator-foreground = ${colors.foreground}
+
+indicator-icon-0 = caps lock;;󰪛
+indicator-icon-1 = scroll lock;;󰹺
+indicator-icon-2 = num lock;;󰼎
+
+label-indicator-on-capslock = %icon%
+label-indicator-off-capslock = %icon%
+label-indicator-on-numlock = %icon%
+label-indicator-off-numlock = %icon%
+label-indicator-on-scrolllock = %icon%
+label-indicator-off-scrolllock = %icon%
\ No newline at end of file
diff --git a/home-config/polybar/modules/memory.ini b/home-config/polybar/modules/memory.ini
new file mode 100644 (file)
index 0000000..877d948
--- /dev/null
@@ -0,0 +1,6 @@
+[module/memory]
+type = internal/memory
+interval = 3
+warn-percentage = 95
+format = <label>
+label = 󰍛 %percentage_used%%
diff --git a/home-config/polybar/modules/network.ini b/home-config/polybar/modules/network.ini
new file mode 100644 (file)
index 0000000..efa4e18
--- /dev/null
@@ -0,0 +1,16 @@
+[module/network]
+type = internal/network
+
+interface = eth0
+interface-type = wired
+interval = 3.0
+speed-unit = 'KB/s'
+
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+
+
+label-connected = 󰖩  %downspeed:5% / %upspeed:5%
+label-connected-foreground = ${colors.secondary}
+label-disconnected = 󰤮
+label-disconnected-foreground = ${colors.alert}
diff --git a/home-config/polybar/modules/pulseaudio.ini b/home-config/polybar/modules/pulseaudio.ini
new file mode 100644 (file)
index 0000000..b33f4e8
--- /dev/null
@@ -0,0 +1,13 @@
+[module/pulseaudio]
+type = internal/pulseaudio
+format-volume-prefix = "墳 "
+#format-volume-prefix-background = ${colors.background}
+format-volume-prefix-foreground = ${colors.foreground}
+format-volume = <label-volume>
+format-volume-padding = 2
+#format-volume-background = ${colors.background}
+label-volume = %percentage%%
+label-muted = 婢 SHHH! 
+label-muted-foreground = ${colors.secondary}
+#label-muted-background = ${colors.background}
+label-muted-padding = 2
diff --git a/home-config/polybar/modules/slackware.ini b/home-config/polybar/modules/slackware.ini
new file mode 100644 (file)
index 0000000..5d27393
--- /dev/null
@@ -0,0 +1,10 @@
+[module/slackware]
+type = custom/script
+exec = slack-updates
+interval = 600
+click-left = slack-updates -n
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/home-config/polybar/modules/temperature.ini b/home-config/polybar/modules/temperature.ini
new file mode 100644 (file)
index 0000000..3bb0621
--- /dev/null
@@ -0,0 +1,54 @@
+[module/temperature]
+type = internal/temperature
+
+; Seconds to sleep between updates
+; Default: 1
+interval = 5
+
+; display units
+units = true
+
+; Thermal zone to use
+; To list all the zone types, run 
+; $ for i in /sys/class/thermal/thermal_zone*; do echo "$i: $(<$i/type)"; done
+; Default: 0
+thermal-zone = 6
+
+; Full path of temperature sysfs path
+; Use `sensors` to find preferred temperature source, then run
+; $ for i in /sys/class/hwmon/hwmon*/temp*_input; do echo "$(<$(dirname $i)/name): $(cat ${i%_*}_label 2>/dev/null || echo $(basename ${i%_*})) $(readlink -f $i)"; done
+; to find path to desired file
+; Default reverts to thermal zone setting
+; hwmon-path = /sys/devices/platform/coretemp.0/hwmon/hwmon4/temp1_input
+hwmon-path = /sys/devices/virtual/thermal/thermal_zone6/hwmon2/temp1_input
+
+; Base temperature for where to start the ramp (in degrees celsius)
+; Default: 0
+base-temperature = 20
+
+; Threshold temperature to display warning label (in degrees celsius)
+; Default: 80
+warn-temperature = 80
+
+; Available tags:
+;   <label> (default)
+;   <ramp>
+format = <label>
+
+; Available tags:
+;   <label-warn> (default)
+;   <ramp>
+format-warn = <label-warn>
+
+; Available tokens:
+;   %temperature% (deprecated)
+;   %temperature-c%   (default, temperature in °C)
+;   %temperature-f%   (temperature in °F)
+label = TEMP %temperature-c%
+
+; Available tokens:
+;   %temperature% (deprecated)
+;   %temperature-c%   (default, temperature in °C)
+;   %temperature-f%   (temperature in °F)
+label-warn = TEMP %temperature-c%
+label-warn-foreground = #f00
\ No newline at end of file
diff --git a/home-config/polybar/modules/tray.ini b/home-config/polybar/modules/tray.ini
new file mode 100644 (file)
index 0000000..f496790
--- /dev/null
@@ -0,0 +1,6 @@
+[module/tray]
+type = internal/tray
+
+tray-padding = 2px
+tray-spacing = 2px
+tray-size = 90%
diff --git a/home-config/polybar/modules/weather.ini b/home-config/polybar/modules/weather.ini
new file mode 100644 (file)
index 0000000..eb7f59b
--- /dev/null
@@ -0,0 +1,10 @@
+[module/weather]
+type = custom/script
+exec = ~/.config/polybar/modules/weather/openweathermap-simple.sh
+interval = 600
+click-left = xdg-open https://openweathermap.org/city/3164527
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/home-config/polybar/modules/weather/openweathermap-simple.sh b/home-config/polybar/modules/weather/openweathermap-simple.sh
new file mode 100755 (executable)
index 0000000..965d8b3
--- /dev/null
@@ -0,0 +1,329 @@
+#!/bin/bash
+
+# SETTINGS vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+
+# API settings ________________________________________________________________
+
+APIKEY='aecd76a64e7f34dd2aae9249ca5d1304'
+# if you leave these empty location will be picked based on your ip-adres
+CITY_NAME='Verona'
+COUNTRY_CODE='IT'
+# Desired output language
+LANG="en"
+# UNITS can be "metric", "imperial" or "kelvin". Set KNOTS to "yes" if you
+# want the wind in knots:
+
+#          | temperature | wind
+# -----------------------------------
+# metric   | Celsius     | km/h
+# imperial | Fahrenheit  | miles/hour
+# kelvin   | Kelvin      | km/h
+
+UNITS="metric"
+
+# Color Settings ______________________________________________________________
+
+COLOR_CLOUD="#606060"
+COLOR_THUNDER="#d3b987"
+COLOR_LIGHT_RAIN="#73cef4"
+COLOR_HEAVY_RAIN="#b3deef"
+COLOR_SNOW="#FFFFFF"
+COLOR_FOG="#606060"
+COLOR_TORNADO="#d3b987"
+COLOR_SUN="#ffc24b"
+COLOR_MOON="#FFFFFF"
+COLOR_ERR="#f43753"
+COLOR_WIND="#73cef4"
+COLOR_COLD="#b3deef"
+COLOR_HOT="#f43753"
+COLOR_NORMAL_TEMP="#FFFFFF"
+
+# Leave "" if you want the default polybar color
+COLOR_TEXT=""
+# Polybar settings ____________________________________________________________
+
+# Font for the weather icons
+WEATHER_FONT_CODE=4
+
+# Font for the thermometer icon
+TEMP_FONT_CODE=2
+
+# Wind settings _______________________________________________________________
+
+# Display info about the wind or not. yes/no
+DISPLAY_WIND="yes"
+
+# Show beaufort level in windicon
+BEAUFORTICON="yes"
+
+# Display in knots. yes/no
+KNOTS="yes"
+
+# How many decimals after the floating point
+DECIMALS=0
+
+# Min. wind force required to display wind info (it depends on what
+# measurement unit you have set: knots, m/s or mph). Set to 0 if you always
+# want to display wind info. It's ignored if DISPLAY_WIND is false.
+
+MIN_WIND=11
+
+# Display the numeric wind force or not. If not, only the wind icon will
+# appear. yes/no
+
+DISPLAY_FORCE="yes"
+
+# Display the wind unit if wind force is displayed. yes/no
+DISPLAY_WIND_UNIT="yes"
+
+# Thermometer settings ________________________________________________________
+
+# When the thermometer icon turns red
+HOT_TEMP=25
+
+# When the thermometer icon turns blue
+COLD_TEMP=0
+
+# Other settings ______________________________________________________________
+
+# Display the weather description. yes/no
+DISPLAY_LABEL="no"
+
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+if [ "$COLOR_TEXT" != "" ]; then
+    COLOR_TEXT_BEGIN="%{F$COLOR_TEXT}"
+    COLOR_TEXT_END="%{F-}"
+fi
+if [ -z "$CITY_NAME" ]; then
+    IP=`curl -s ifconfig.me`  # == ip
+    IPCURL=$(curl -s https://ipinfo.io/$IP)
+    CITY_NAME=$(echo $IPCURL | jq -r ".city")
+    COUNTRY_CODE=$(echo $IPCURL | jq -r ".country")
+fi
+
+RESPONSE=""
+ERROR=0
+ERR_MSG=""
+if [ $UNITS = "kelvin" ]; then
+    UNIT_URL=""
+else
+    UNIT_URL="&units=$UNITS"
+fi
+URL="api.openweathermap.org/data/2.5/weather?appid=$APIKEY$UNIT_URL&lang=$LANG&q=$(echo $CITY_NAME| sed 's/ /%20/g'),${COUNTRY_CODE}"
+
+function getData {
+    ERROR=0
+    # For logging purposes
+    # echo " " >> "$HOME/.weather.log"
+    # echo `date`" ################################" >> "$HOME/.weather.log"
+    RESPONSE=`curl -s $URL`
+    CODE="$?"
+    if [ "$1" = "-d" ]; then
+        echo $RESPONSE
+        echo ""
+    fi
+    # echo "Response: $RESPONSE" >> "$HOME/.weather.log"
+    RESPONSECODE=0
+    if [ $CODE -eq 0 ]; then
+        RESPONSECODE=`echo $RESPONSE | jq .cod`
+    fi
+    if [ $CODE -ne 0 ] || [ ${RESPONSECODE:=429} -ne 200 ]; then
+        if [ $CODE -ne 0 ]; then
+            ERR_MSG="curl Error $CODE"
+            # echo "curl Error $CODE" >> "$HOME/.weather.log"
+        else
+            ERR_MSG="Conn. Err. $RESPONSECODE"
+            # echo "API Error $RESPONSECODE" >> "$HOME/.weather.log"
+        fi
+        ERROR=1
+    # else
+    #     echo "$RESPONSE" > "$HOME/.weather-last"
+    #     echo `date +%s` >> "$HOME/.weather-last"
+    fi
+}
+function setIcons {
+    if [ $WID -le 232 ]; then
+        #Thunderstorm
+        ICON_COLOR=$COLOR_THUNDER
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 311 ]; then
+        #Light drizzle
+        ICON_COLOR=$COLOR_LIGHT_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 321 ]; then
+        #Heavy drizzle
+        ICON_COLOR=$COLOR_HEAVY_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 531 ]; then
+        #Rain
+        ICON_COLOR=$COLOR_HEAVY_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 622 ]; then
+        #Snow
+        ICON_COLOR=$COLOR_SNOW
+        ICON=""
+    elif [ $WID -le 771 ]; then
+        #Fog
+        ICON_COLOR=$COLOR_FOG
+        ICON=""
+    elif [ $WID -eq 781 ]; then
+        #Tornado
+        ICON_COLOR=$COLOR_TORNADO
+        ICON=""
+    elif [ $WID -eq 800 ]; then
+        #Clear sky
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON_COLOR=$COLOR_SUN
+            ICON=""
+        else
+            ICON_COLOR=$COLOR_MOON
+            ICON=""
+        fi
+    elif [ $WID -eq 801 ]; then
+        # Few clouds
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON_COLOR=$COLOR_SUN
+            ICON=""
+        else
+            ICON_COLOR=$COLOR_MOON
+            ICON=""
+        fi
+    elif [ $WID -le 804 ]; then
+        # Overcast
+        ICON_COLOR=$COLOR_CLOUD
+        ICON=""
+    else
+        ICON_COLOR=$COLOR_ERR
+        ICON=""
+    fi
+    WIND=""
+    WINDFORCE=`echo "$RESPONSE" | jq .wind.speed`
+    WINDICON=""
+    if [ $BEAUFORTICON == "yes" ];then
+        WINDFORCE2=`echo "scale=$DECIMALS;$WINDFORCE * 3.6 / 1" | bc`
+        if [ $WINDFORCE2 -le 1 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 1 ] && [ $WINDFORCE2 -le 5 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 5 ] && [ $WINDFORCE2 -le 11 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 11 ] && [ $WINDFORCE2 -le 19 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 19 ] && [ $WINDFORCE2 -le 28 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 28 ] && [ $WINDFORCE2 -le 38 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 38 ] && [ $WINDFORCE2 -le 49 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 49 ] && [ $WINDFORCE2 -le 61 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 61 ] && [ $WINDFORCE2 -le 74 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 74 ] && [ $WINDFORCE2 -le 88 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 88 ] && [ $WINDFORCE2 -le 102 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 102 ] && [ $WINDFORCE2 -le 117 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 117 ]; then
+            WINDICON=""
+        fi
+    fi
+    if [ $KNOTS = "yes" ]; then
+        case $UNITS in
+            "imperial") 
+                # The division by one is necessary because scale works only for divisions. bc is stupid.
+                WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 0.8689762419 / 1" | bc`
+                ;;
+            *)
+                WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 1.943844 / 1" | bc`
+                ;;
+        esac
+    else
+        if [ $UNITS != "imperial" ]; then
+            # Conversion from m/s to km/h
+            WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 3.6 / 1" | bc`
+        else
+            WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE / 1" | bc`
+        fi
+    fi
+    if [ "$DISPLAY_WIND" = "yes" ] && [ `echo "$WINDFORCE >= $MIN_WIND" |bc -l` -eq 1 ]; then
+        WIND="%{T$WEATHER_FONT_CODE}%{F$COLOR_WIND}$WINDICON%{F-}%{T-}"
+        if [ $DISPLAY_FORCE = "yes" ]; then
+            WIND="$WIND $COLOR_TEXT_BEGIN$WINDFORCE$COLOR_TEXT_END"
+            if [ $DISPLAY_WIND_UNIT = "yes" ]; then
+                if [ $KNOTS = "yes" ]; then
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}kn$COLOR_TEXT_END"
+                elif [ $UNITS = "imperial" ]; then
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}mph$COLOR_TEXT_END"
+                else
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}km/h$COLOR_TEXT_END"
+                fi
+            fi
+        fi
+        WIND="$WIND |"
+    fi
+    if [ "$UNITS" = "metric" ]; then
+        TEMP_ICON="󰔄"
+    elif [ "$UNITS" = "imperial" ]; then
+        TEMP_ICON="󰔅"
+    else
+        TEMP_ICON="󰔆"
+    fi
+    
+    TEMP=`echo "$TEMP" | cut -d "." -f 1`
+    
+    if [ "$TEMP" -le $COLD_TEMP ]; then
+        TEMP="%{F$COLOR_COLD}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    elif [ `echo "$TEMP >= $HOT_TEMP" | bc` -eq 1 ]; then
+        TEMP="%{F$COLOR_HOT}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    else
+        TEMP="%{F$COLOR_NORMAL_TEMP}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    fi
+}
+
+function outputCompact {
+    OUTPUT="$WIND %{T$WEATHER_FONT_CODE}%{F$ICON_COLOR}$ICON%{F-}%{T-} $ERR_MSG$COLOR_TEXT_BEGIN $DESCRIPTION$COLOR_TEXT_END| $TEMP"
+    # echo "Output: $OUTPUT" >> "$HOME/.weather.log"
+    echo "$OUTPUT"
+}
+
+getData $1
+if [ $ERROR -eq 0 ]; then
+    MAIN=`echo $RESPONSE | jq .weather[0].main`
+    WID=`echo $RESPONSE | jq .weather[0].id`
+    DESC=`echo $RESPONSE | jq .weather[0].description`
+    SUNRISE=`echo $RESPONSE | jq .sys.sunrise`
+    SUNSET=`echo $RESPONSE | jq .sys.sunset`
+    DATE=`date +%s`
+    WIND=""
+    TEMP=`echo $RESPONSE | jq .main.temp`
+    if [ $DISPLAY_LABEL = "yes" ]; then
+        DESCRIPTION=`echo "$RESPONSE" | jq .weather[0].description | tr -d '"' | awk '{for (i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1'`" "
+    else
+        DESCRIPTION=""
+    fi
+    PRESSURE=`echo $RESPONSE | jq .main.pressure`
+    HUMIDITY=`echo $RESPONSE | jq .main.humidity`
+    setIcons
+    outputCompact
+else
+    echo " "
+fi
diff --git a/home-config/polybar/modules/weather/openweathermap-simple.sh.old b/home-config/polybar/modules/weather/openweathermap-simple.sh.old
new file mode 100755 (executable)
index 0000000..8d2c477
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+get_icon() {
+    case $1 in
+        # Icons for weather-icons
+        01d) icon="";;
+        01n) icon="";;
+        02d) icon="";;
+        02n) icon="";;
+        03*) icon="󰖐";;
+        04d) icon="󰖕";;
+        04n) icon="󰼱";;
+        09*) icon="󰖗";;
+        10d) icon="";;
+        10n) icon="";;
+        11d) icon="";;
+        11n) icon="";;
+        13d) icon="";;
+        13n) icon="";;
+        50d) icon="";;
+        50n) icon="";;
+        *) icon="󰨹";
+    esac
+
+    echo $icon
+}
+
+KEY="aecd76a64e7f34dd2aae9249ca5d1304"
+CITY="3164527"
+UNITS="metric"
+SYMBOL="°"
+
+API="https://api.openweathermap.org/data/2.5"
+
+if [ -n "$CITY" ]; then
+    if [ "$CITY" -eq "$CITY" ] 2>/dev/null; then
+        CITY_PARAM="id=$CITY"
+    else
+        CITY_PARAM="q=$CITY"
+    fi
+
+    weather=$(curl -sf "$API/weather?appid=$KEY&$CITY_PARAM&units=$UNITS")
+else
+    location=$(curl -sf "https://location.services.mozilla.com/v1/geolocate?key=geoclue")
+
+    if [ -n "$location" ]; then
+        location_lat="$(echo "$location" | jq '.location.lat')"
+        location_lon="$(echo "$location" | jq '.location.lng')"
+
+        weather=$(curl -sf "$API/weather?appid=$KEY&lat=$location_lat&lon=$location_lon&units=$UNITS")
+    fi
+fi
+
+if [ -n "$weather" ]; then
+    weather_temp=$(echo "$weather" | jq ".main.temp" | cut -d "." -f 1)
+    weather_icon=$(echo "$weather" | jq -r ".weather[0].icon")
+
+    echo "$(get_icon "$weather_icon")" "$weather_temp$SYMBOL"
+fi
diff --git a/home-config/polybar/modules/windowlist.ini b/home-config/polybar/modules/windowlist.ini
new file mode 100644 (file)
index 0000000..a1a5f0c
--- /dev/null
@@ -0,0 +1,5 @@
+[module/windowlist]
+type = custom/script
+exec = ~/.config/polybar/scripts/windowlist/main 2> /dev/null
+tail = true
+
diff --git a/home-config/polybar/modules/windows.ini b/home-config/polybar/modules/windows.ini
new file mode 100644 (file)
index 0000000..c53f186
--- /dev/null
@@ -0,0 +1,11 @@
+[module/xwindow]
+type = internal/xwindow
+
+format = <label>
+format-foreground = ${colors.foreground}
+
+label = %title%
+label-maxlen = 30
+
+label-empty = "¯\_(ツ)_/¯"
+
diff --git a/home-config/polybar/modules/workspaces.ini b/home-config/polybar/modules/workspaces.ini
new file mode 100644 (file)
index 0000000..ff0687e
--- /dev/null
@@ -0,0 +1,37 @@
+[module/xworkspaces]
+type = internal/xworkspaces
+
+pin-workspaces = true
+enable-click = true
+enable-scroll = true
+
+label-active = %icon%
+#label-active-background = ${colors.background}
+label-active-foreground = ${colors.secondary}
+label-active-underline= ${colors.secondary}
+label-active-padding = 2
+
+label-occupied = %icon%
+#label-occupied-background = ${colors.background}
+label-occupied-padding = 2
+
+label-urgent = %icon%
+#label-urgent-background = ${colors.background}
+label-urgent-foreground = ${colors.alert}
+label-urgent-padding = 2
+
+label-empty = %icon%
+#label-empty-background = ${colors.background}
+label-empty-foreground = ${colors.foreground-alt}
+label-empty-padding = 2
+
+icon-0 = web;󰖟
+icon-1 = editor;
+icon-2 = console;󰆍
+icon-3 = ssh;󰢩
+icon-4 = graphic;󰽉
+icon-5 = editor2;󰼭
+icon-6 = chat;󰭻
+icon-7 = gaming;󰊴
+icon-default = 
+
diff --git a/home-config/polybar/scripts/windowlist/LICENSE b/home-config/polybar/scripts/windowlist/LICENSE
new file mode 100644 (file)
index 0000000..a9fce23
--- /dev/null
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2020 aroma1994
+Copyright (c) 2022 tuurep
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/home-config/polybar/scripts/windowlist/Makefile b/home-config/polybar/scripts/windowlist/Makefile
new file mode 100644 (file)
index 0000000..c5300da
--- /dev/null
@@ -0,0 +1,26 @@
+CFLAGS = -g -O2 -Wall
+LDFLAGS = -lX11
+
+all: main windowlist.o click-actions/raise click-actions/minimize click-actions/close
+
+main: main.c windowlist.o windowlist.h toml-c.h
+       gcc $(CFLAGS) $(LDFLAGS) -o main main.c windowlist.o
+
+windowlist.o: windowlist.c
+       gcc $(CFLAGS) -c windowlist.c
+
+click-actions/raise: click-actions/raise.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/raise click-actions/raise.c
+
+click-actions/minimize: click-actions/minimize.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/minimize click-actions/minimize.c
+
+click-actions/close: click-actions/close.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/close click-actions/close.c
+
+clean:
+       rm windowlist.o
+       rm main
+       rm click-actions/raise
+       rm click-actions/minimize
+       rm click-actions/close
diff --git a/home-config/polybar/scripts/windowlist/README.md b/home-config/polybar/scripts/windowlist/README.md
new file mode 100644 (file)
index 0000000..2ecf707
--- /dev/null
@@ -0,0 +1,229 @@
+# windowlist
+
+![screenshot](screenshot.png)
+
+Began as a fork of my favorite Polybar script [polywins](https://github.com/uniquepointer/polywins)
+
+Windowlist has been fully rewritten in C using the relevant parts of the source code from [wmctrl](https://github.com/Conservatory/wmctrl) and [xprop](https://gitlab.freedesktop.org/xorg/app/xprop).
+
+## Improvements over polywins
+
+* Fixed a bug where names would not be correct if WM_CLASS contains spaces or dots
+* Option to sort the window list:
+    * By horizontal position on the screen
+    * By the application name
+* Ability to set nicknames for windows if a window has a bad default name
+* More flexible styling
+* Configurable click actions
+
+## Installation
+
+Project directory should be in `~/.config/polybar/scripts/`
+
+In `~/.config/polybar/scripts/windowlist/` run `make`
+
+Add module in `~/.config/polybar/config.ini`:
+
+```ini
+[module/windowlist]
+type = custom/script
+exec = ~/.config/polybar/scripts/windowlist/main 2> /dev/null
+tail = true
+```
+
+Add module `windowlist` in any of `modules-left`, `modules-center` or `modules-right`
+
+## Configuration
+
+Windowlist can be configured in `config.toml` in the root of the project.
+
+All options are detailed below:
+
+<table>
+    <tbody>
+        <tr>
+            <th>Option</th>
+            <th>Description</th>
+            <th>Possible values</th>
+        </tr>
+        <tr>
+            <td><code>sort_by</code></td>
+            <td>Criteria to sort the list of windows</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: unordered (WM client list order)</li>
+                    <li><code>"position"</code>: sort based on horizontal position on the screen</li>
+                    <li><code>"application"</code>: sort alphabetically based on the application class</li>
+                <ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>max_windows</code></td>
+            <td>How many windows can be visible on the list. Number of windows that did not fit will be shown e.g. <code>(+3)</code></td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>name</code></td>
+            <td>Which X window property is considered window name (label for a window)</td>
+            <td>
+                <ul>
+                    <li><code>"class"</code>: WM_CLASS</li>
+                    <li><code>"title"</code>: WM_NAME</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>name_case</code></td>
+            <td>Text case for window names</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: don't change capitalization</li>
+                    <li><code>"lowercase"</code>: all lowercase</li>
+                    <li><code>"uppercase"</code>: all uppercase</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>name_max_length</code></td>
+            <td>Maximum length for a window name before it's truncated with <code>‥</code></td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>name_padding</code></td>
+            <td>How many spaces to add before and after a window name</td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>separator_string</code></td>
+            <td>String displayed between window names</td>
+            <td align="center">any string</td>
+        </tr>
+        <tr>
+            <td><code>empty_desktop_string</code></td>
+            <td>String to show when no windows are open</td>
+            <td align="center">any string</td>
+        </tr>
+        <tr>
+            <td>
+                <code>active_window_left_click</code><br>
+                <code>active_window_middle_click</code><br>
+                <code>active_window_right_click</code><br>
+                <code>active_window_scroll_up</code><br>
+                <code>active_window_scroll_down</code><br>
+                <code>inactive_window_left_click</code><br>
+                <code>inactive_window_middle_click</code><br>
+                <code>inactive_window_right_click</code><br>
+                <code>inactive_window_scroll_up</code><br>
+                <code>inactive_window_scroll_down</code><br>
+            </td>
+            <td>Click actions for window names can be set as <code>"raise"</code>, <code>"minimize"</code> or <code>"close"</code>, or a custom script/program in the <code>click-actions</code> directory. Window currently in focus (active) and unfocused windows (inactive) are configurable separately.</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no action</li>
+                    <li>name of script (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <code>active_window_fg_color</code>
+                <code>inactive_window_fg_color</code>
+                <code>separator_fg_color</code>
+                <code>empty_desktop_fg_color</code>
+                <code>overflow_fg_color</code>
+            </td>
+            <td>
+                Foreground colors for:
+                <ul>
+                    <li>Currently focused window</li>
+                    <li>Windows not in focus</li>
+                    <li>The <code>separator_string</code></li>
+                    <li>The <code>empty_desktop_string</code></li>
+                    <li>The string shown when <code>max_windows</code> exceeded</li>
+                </ul>
+            </td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: default polybar fg</code></li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>*_bg_color</code></td>
+            <td>All of the foreground colors have a background color counterpart, e.g. <code>active_window_bg_color</code></td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no background color</li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>*_ul_color</code></td>
+            <td>All colors also have an underline color counterpart, e.g. <code>active_window_ul_color</code><br><br>
+            Note that <code>line-size</code> must be set to 1 or higher in your polybar <code>config.ini</code>, otherwise underline isn't visible.</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no underline</li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>ignored_classes</code></td>
+            <td>Windows with a WM_CLASS in this array will not be shown on the bar. Strings are matched case insensitively.</td>
+            <td align="center">array of strings</td>
+        </tr>
+        <tr>
+            <td><code>window_nicknames</code></td>
+            <td>A window name can be substituted with a custom name using key value pairs. The keys are matched case insensitively.</td>
+            <td align="center">table of string key-value pairs</td>
+        </tr>
+    </tbody>
+</table>
+
+Note: polybar must be reset before changes take effect.
+
+### Defaults
+
+Check the `config.toml` in this repo, the options set there are the default values.
+
+You can also remove any key and it will fall back to the default value.
+
+### Scripting click actions
+
+The most convenient way is to write a shell script in the `click-actions` directory. Any language could be used, though. There are three "default" actions as small C programs: `raise`, `minimize` and `close`.
+
+You can write a new action as a script such as:
+
+`click-actions/foo.sh`
+
+```bash
+#!/bin/sh
+
+window_id="$1"
+
+# Do something with the window id of the window that has been clicked/scrolled on
+```
+
+Set the script as executable: `chmod +x click-actions/foo.sh`
+
+Then in `config.toml`:
+
+```toml
+active_window_middle_click = "foo.sh"
+```
+
+Window id is always given as arg `$1`. Tools I know that could be used to make something happen with a window id:
+
+* [wmctrl](https://github.com/Conservatory/wmctrl)
+* [wmutils](https://github.com/wmutils/core)
+* [xdo](https://github.com/baskerville/xdo)
+* [xdotool](https://github.com/jordansissel/xdotool)
+
+## Dependencies
+
+Requires an [EWMH](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html) compliant window manager
+
+* Wikipedia list of EWMH compliant window managers: [link](https://en.wikipedia.org/wiki/Extended_Window_Manager_Hints#List_of_window_managers_that_support_Extended_Window_Manager_Hints)
diff --git a/home-config/polybar/scripts/windowlist/click-actions/close b/home-config/polybar/scripts/windowlist/click-actions/close
new file mode 100755 (executable)
index 0000000..5220de1
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/click-actions/close differ
diff --git a/home-config/polybar/scripts/windowlist/click-actions/close.c b/home-config/polybar/scripts/windowlist/click-actions/close.c
new file mode 100644 (file)
index 0000000..f44dd19
--- /dev/null
@@ -0,0 +1,46 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+int client_msg(Display* d, Window w, char* msg) {
+    XEvent e;
+    long mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+    e.xclient.type = ClientMessage;
+    e.xclient.serial = 0;
+    e.xclient.send_event = True;
+    e.xclient.message_type = XInternAtom(d, msg, False);
+    e.xclient.window = w;
+    e.xclient.format = 32;
+    e.xclient.data.l[0] = 0;
+    e.xclient.data.l[1] = 0;
+    e.xclient.data.l[2] = 0;
+    e.xclient.data.l[3] = 0;
+    e.xclient.data.l[4] = 0;
+
+    if (XSendEvent(d, DefaultRootWindow(d), False, mask, &e)) {
+        return EXIT_SUCCESS;
+    } else {
+        fprintf(stderr, "Cannot send %s event.\n", msg);
+        return EXIT_FAILURE;
+    }
+}
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    client_msg(d, wid, "_NET_CLOSE_WINDOW");
+    XCloseDisplay(d);
+}
diff --git a/home-config/polybar/scripts/windowlist/click-actions/minimize b/home-config/polybar/scripts/windowlist/click-actions/minimize
new file mode 100755 (executable)
index 0000000..10a671e
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/click-actions/minimize differ
diff --git a/home-config/polybar/scripts/windowlist/click-actions/minimize.c b/home-config/polybar/scripts/windowlist/click-actions/minimize.c
new file mode 100644 (file)
index 0000000..010151d
--- /dev/null
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    XIconifyWindow(d, wid, DefaultScreen(d));
+    XCloseDisplay(d);
+}
diff --git a/home-config/polybar/scripts/windowlist/click-actions/raise b/home-config/polybar/scripts/windowlist/click-actions/raise
new file mode 100755 (executable)
index 0000000..44499ab
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/click-actions/raise differ
diff --git a/home-config/polybar/scripts/windowlist/click-actions/raise.c b/home-config/polybar/scripts/windowlist/click-actions/raise.c
new file mode 100644 (file)
index 0000000..9818508
--- /dev/null
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+int client_msg(Display* d, Window w, char* msg) {
+    XEvent e;
+    long mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+    e.xclient.type = ClientMessage;
+    e.xclient.serial = 0;
+    e.xclient.send_event = True;
+    e.xclient.message_type = XInternAtom(d, msg, False);
+    e.xclient.window = w;
+    e.xclient.format = 32;
+    e.xclient.data.l[0] = 0;
+    e.xclient.data.l[1] = 0;
+    e.xclient.data.l[2] = 0;
+    e.xclient.data.l[3] = 0;
+    e.xclient.data.l[4] = 0;
+
+    if (XSendEvent(d, DefaultRootWindow(d), False, mask, &e)) {
+        return EXIT_SUCCESS;
+    } else {
+        fprintf(stderr, "Cannot send %s event.\n", msg);
+        return EXIT_FAILURE;
+    }
+}
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    client_msg(d, wid, "_NET_ACTIVE_WINDOW");
+    XMapRaised(d, wid);
+    XCloseDisplay(d);
+}
diff --git a/home-config/polybar/scripts/windowlist/config.toml b/home-config/polybar/scripts/windowlist/config.toml
new file mode 100644 (file)
index 0000000..ee49692
--- /dev/null
@@ -0,0 +1,52 @@
+sort_by = "none" # "none" | "position" | "application"
+max_windows = 13
+
+name = "class" # "class" | "title"
+name_case = "lowercase" # "none" | "lowercase" | "uppercase"
+name_max_length = 30
+name_padding = 1
+
+separator_string = "|"
+#empty_desktop_string = "¯\_(ツ)_/¯"
+empty_desktop_string = " lol "
+
+active_window_left_click   = "minimize"
+active_window_middle_click = "none"
+active_window_right_click  = "close"
+active_window_scroll_up    = "none"
+active_window_scroll_down  = "none"
+
+inactive_window_left_click   = "raise"
+inactive_window_middle_click = "none"
+inactive_window_right_click  = "close"
+inactive_window_scroll_up    = "none"
+inactive_window_scroll_down  = "none"
+
+active_window_fg_color = "none"
+active_window_bg_color = "none"
+active_window_ul_color = "none"
+
+inactive_window_fg_color = "#808080"
+inactive_window_bg_color = "none"
+inactive_window_ul_color = "none"
+
+separator_fg_color = "#808080"
+separator_bg_color = "none"
+separator_ul_color = "none"
+
+empty_desktop_fg_color = "none"
+empty_desktop_bg_color = "none"
+empty_desktop_ul_color = "none"
+
+overflow_fg_color = "none"
+overflow_bg_color = "none"
+overflow_ul_color = "none"
+
+ignored_classes = [
+    # "foo",
+    # "bar"
+]
+
+[window_nicknames]
+# foo = "bar"
+# baz = "qux"
diff --git a/home-config/polybar/scripts/windowlist/main b/home-config/polybar/scripts/windowlist/main
new file mode 100755 (executable)
index 0000000..c4e940d
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/main differ
diff --git a/home-config/polybar/scripts/windowlist/main.c b/home-config/polybar/scripts/windowlist/main.c
new file mode 100644 (file)
index 0000000..a8f765a
--- /dev/null
@@ -0,0 +1,450 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <X11/Xlib.h>
+#include "toml-c.h"
+#include "windowlist.h"
+
+#define MAX_STR_LEN 200
+
+struct configuration {
+    char* sort_by;
+    int max_windows;
+
+    char* name;
+    char* name_case;
+    int name_max_length;
+    int name_padding;
+
+    char* empty_desktop_string;
+    char* separator_string;
+
+    char* active_window_left_click;
+    char* active_window_middle_click;
+    char* active_window_right_click;
+    char* active_window_scroll_up;
+    char* active_window_scroll_down;
+
+    char* inactive_window_left_click;
+    char* inactive_window_middle_click;
+    char* inactive_window_right_click;
+    char* inactive_window_scroll_up;
+    char* inactive_window_scroll_down;
+
+    char* active_window_fg_color;
+    char* active_window_bg_color;
+    char* active_window_ul_color;
+
+    char* inactive_window_fg_color;
+    char* inactive_window_bg_color;
+    char* inactive_window_ul_color;
+
+    char* separator_fg_color;
+    char* separator_bg_color;
+    char* separator_ul_color;
+
+    char* empty_desktop_fg_color;
+    char* empty_desktop_bg_color;
+    char* empty_desktop_ul_color;
+
+    char* overflow_fg_color;
+    char* overflow_bg_color;
+    char* overflow_ul_color;
+
+    toml_array_t* ignored_classes;
+    toml_table_t* window_nicknames;
+} config;
+
+toml_table_t* parse_config(char* filename, char* path) {
+    char config_path[MAX_STR_LEN];
+    snprintf(config_path, MAX_STR_LEN, "%s/%s", path, filename);
+
+    char errbuf[MAX_STR_LEN];
+
+    FILE* fp = fopen(config_path, "r");
+    toml_table_t* tbl = toml_parse_file(fp, errbuf, sizeof(errbuf));
+    fclose(fp);
+
+    toml_value_t opt;
+
+    opt = toml_table_string(tbl, "sort_by");  config.sort_by     = opt.ok ? opt.u.s : "none";
+    opt = toml_table_int(tbl, "max_windows"); config.max_windows = opt.ok ? opt.u.i : 13;
+
+    opt = toml_table_string(tbl, "name");         config.name            = opt.ok ? opt.u.s : "class";
+    opt = toml_table_string(tbl, "name_case");    config.name_case       = opt.ok ? opt.u.s : "lowercase";
+    opt = toml_table_int(tbl, "name_max_length"); config.name_max_length = opt.ok ? opt.u.i : 30;
+    opt = toml_table_int(tbl, "name_padding");    config.name_padding    = opt.ok ? opt.u.i : 1;
+
+    opt = toml_table_string(tbl, "empty_desktop_string"); config.empty_desktop_string = opt.ok ? opt.u.s : "";
+    opt = toml_table_string(tbl, "separator_string");     config.separator_string     = opt.ok ? opt.u.s : "·";
+
+    opt = toml_table_string(tbl, "active_window_left_click");   config.active_window_left_click   = opt.ok ? opt.u.s : "minimize";
+    opt = toml_table_string(tbl, "active_window_middle_click"); config.active_window_middle_click = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_right_click");  config.active_window_right_click  = opt.ok ? opt.u.s : "close";
+    opt = toml_table_string(tbl, "active_window_scroll_up");    config.active_window_scroll_up    = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_scroll_down");  config.active_window_scroll_down  = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "inactive_window_left_click");   config.inactive_window_left_click   = opt.ok ? opt.u.s : "raise";
+    opt = toml_table_string(tbl, "inactive_window_middle_click"); config.inactive_window_middle_click = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_right_click");  config.inactive_window_right_click  = opt.ok ? opt.u.s : "close";
+    opt = toml_table_string(tbl, "inactive_window_scroll_up");    config.inactive_window_scroll_up    = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_scroll_down");  config.inactive_window_scroll_down  = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "active_window_fg_color"); config.active_window_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_bg_color"); config.active_window_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_ul_color"); config.active_window_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "inactive_window_fg_color"); config.inactive_window_fg_color = opt.ok ? opt.u.s : "#808080";
+    opt = toml_table_string(tbl, "inactive_window_bg_color"); config.inactive_window_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_ul_color"); config.inactive_window_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "separator_fg_color"); config.separator_fg_color = opt.ok ? opt.u.s : "#808080";
+    opt = toml_table_string(tbl, "separator_bg_color"); config.separator_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "separator_ul_color"); config.separator_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "empty_desktop_fg_color"); config.empty_desktop_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "empty_desktop_bg_color"); config.empty_desktop_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "empty_desktop_ul_color"); config.empty_desktop_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "overflow_fg_color"); config.overflow_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "overflow_bg_color"); config.overflow_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "overflow_ul_color"); config.overflow_ul_color = opt.ok ? opt.u.s : "none";
+
+    // It's fine if these are NULL
+    config.ignored_classes  = toml_table_array(tbl, "ignored_classes");
+    config.window_nicknames = toml_table_table(tbl, "window_nicknames");
+
+    return tbl;
+}
+
+void lowercase(char* str) {
+    for(int i = 0; str[i]; i++) {
+        str[i] = tolower(str[i]);
+    }
+}
+
+void uppercase(char* str) {
+    for(int i = 0; str[i]; i++) {
+        str[i] = toupper(str[i]);
+    }
+}
+
+int compare_window_class(const void* v1, const void* v2) {
+    const struct window_props* p1 = v1;
+    const struct window_props* p2 = v2;
+    lowercase(p1->class);
+    lowercase(p2->class);
+    return strcmp(p1->class, p2->class);
+}
+
+int compare_position(const void* v1, const void* v2) {
+    // Sort wlist by horizontal position on screen
+    // If tied, vertical position decides (higher first)
+    const struct window_props* p1 = v1;
+    const struct window_props* p2 = v2;
+    if (p1->x < p2->x) return -1;
+    if (p1->x > p2->x) return 1;
+    if (p1->y < p2->y) return -1;
+    if (p1->y > p2->y) return 1;
+    return 0;
+}
+
+char* pad_spaces(char* window_name) {
+    int padding = config.name_padding;
+
+    int name_length = strlen(window_name);
+    int padded_length = name_length + (2 * padding);
+
+    char* padded_name = malloc(padded_length + 1);
+
+    memset(padded_name, ' ', padded_length);
+    memcpy(padded_name + padding, window_name, name_length);
+    padded_name[padded_length] = '\0';
+
+    return padded_name;
+}
+
+bool is_unused(char* option) {
+    if (option[0] == '\0' || !strcmp(option, "none")) {
+        return true;
+    }
+    return false;
+}
+
+bool is_ignored(char* class) {
+    if (!config.ignored_classes) {
+        return false;
+    }
+
+    for (int i = 0; i < toml_array_len(config.ignored_classes); i++) {
+        char* ignored_class = toml_array_string(config.ignored_classes, i).u.s;
+        if (!strcasecmp(class, ignored_class)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+char* get_window_nickname(char* class, char* title) {
+    if (!config.window_nicknames) {
+        return NULL;
+    }
+
+    for (int i = 0; i < toml_table_len(config.window_nicknames); i++) {
+        int keylen;
+
+        const char* key = toml_table_key(config.window_nicknames, i, &keylen);
+        char* val;
+
+        if (!strcmp(config.name, "title")) {
+            if (!strcasecmp(key, title)) {
+                val = toml_table_string(config.window_nicknames, key).u.s;
+                return val;
+            }
+        } else {
+            if (!strcasecmp(key, class)) {
+                val = toml_table_string(config.window_nicknames, key).u.s;
+                return val;
+            }
+        }
+    }
+    return NULL;
+}
+
+void print_polybar_str(char* label, char* fg_color, char* bg_color, char* ul_color,
+                       char* l_click, char* m_click, char* r_click, char* scroll_up, char* scroll_down) {
+
+    int actions_count = 0;
+
+    if (!is_unused(l_click)) {
+        printf("%%{A1:%s:}", l_click);
+        actions_count++;
+    }
+
+    if (!is_unused(m_click)) {
+        printf("%%{A2:%s:}", m_click);
+        actions_count++;
+    }
+
+    if (!is_unused(r_click)) {
+        printf("%%{A3:%s:}", r_click);
+        actions_count++;
+    }
+
+    if (!is_unused(scroll_up)) {
+        printf("%%{A4:%s:}", scroll_up);
+        actions_count++;
+    }
+
+    if (!is_unused(scroll_down)) {
+        printf("%%{A5:%s:}", scroll_down);
+        actions_count++;
+    }
+
+    if (!is_unused(bg_color)) {
+        printf("%%{B%s}", bg_color);
+    }
+
+    if (!is_unused(ul_color)) {
+        printf("%%{u%s}%%{+u}", ul_color);
+    }
+
+    if (!is_unused(fg_color)) {
+        printf("%%{F%s}", fg_color);
+    }
+
+    printf(label);
+
+    if (!is_unused(fg_color)) {
+        printf("%%{F-}");
+    }
+
+    if (!is_unused(ul_color)) {
+        printf("%%{-u}");
+    }
+
+    if (!is_unused(bg_color)) {
+        printf("%%{B-}");
+    }
+
+    for (int i = 0; i < actions_count; i++) {
+        printf("%%{A}");
+    }
+}
+
+void set_action_str(char* str, char* path, char* option, Window wid) {
+    if (is_unused(option)) {
+        strcpy(str, "none");
+        return;
+    }
+
+    snprintf(str, MAX_STR_LEN, "%s/click-actions/%s 0x%lx", path, option, wid);
+}
+
+void output(struct window_props* wlist, int n, Window active_window, char* path) {
+
+    if (!strcmp(config.sort_by, "application")) {
+        qsort(wlist, n, sizeof(struct window_props), compare_window_class);
+    }
+    if (!strcmp(config.sort_by, "position")) {
+        qsort(wlist, n, sizeof(struct window_props), compare_position);
+    }
+
+    int window_count = 0;
+
+    for (int i = 0; i < n; i++) {
+        if (is_ignored(wlist[i].class)) {
+            continue;
+        }
+
+        if (window_count > config.max_windows) {
+            window_count++;
+            continue;
+        }
+
+        if (window_count > 0) {
+            print_polybar_str(config.separator_string, config.separator_fg_color, config.separator_bg_color, config.separator_ul_color,
+                              "none", "none", "none", "none", "none");
+        }
+
+        char window_left_click  [MAX_STR_LEN];
+        char window_middle_click[MAX_STR_LEN];
+        char window_right_click [MAX_STR_LEN];
+        char window_scroll_up   [MAX_STR_LEN];
+        char window_scroll_down [MAX_STR_LEN];
+        char* window_fg_color;
+        char* window_bg_color;
+        char* window_ul_color;
+
+        if (wlist[i].id != active_window) {
+            set_action_str(window_left_click,   path, config.inactive_window_left_click,   wlist[i].id);
+            set_action_str(window_middle_click, path, config.inactive_window_middle_click, wlist[i].id);
+            set_action_str(window_right_click,  path, config.inactive_window_right_click,  wlist[i].id);
+            set_action_str(window_scroll_up,    path, config.inactive_window_scroll_up,    wlist[i].id);
+            set_action_str(window_scroll_down,  path, config.inactive_window_scroll_down,  wlist[i].id);
+            window_fg_color = config.inactive_window_fg_color;
+            window_bg_color = config.inactive_window_bg_color;
+            window_ul_color = config.inactive_window_ul_color;
+        } else {
+            set_action_str(window_left_click,   path, config.active_window_left_click,   wlist[i].id);
+            set_action_str(window_middle_click, path, config.active_window_middle_click, wlist[i].id);
+            set_action_str(window_right_click,  path, config.active_window_right_click,  wlist[i].id);
+            set_action_str(window_scroll_up,    path, config.active_window_scroll_up,    wlist[i].id);
+            set_action_str(window_scroll_down,  path, config.active_window_scroll_down,  wlist[i].id);
+            window_fg_color = config.active_window_fg_color;
+            window_bg_color = config.active_window_bg_color;
+            window_ul_color = config.active_window_ul_color;
+        }
+
+        char* window_nickname = get_window_nickname(wlist[i].class, wlist[i].title);
+        char* window_name = window_nickname;
+
+        if (!window_nickname) {
+            if (!strcmp(config.name, "title")) {
+                window_name = wlist[i].title;
+            } else {
+                window_name = wlist[i].class;
+            }
+        }
+
+        if (strlen(window_name) > config.name_max_length) {
+            // Name is truncated
+            strcpy(window_name + config.name_max_length, "‥");
+        }
+
+        if (!strcmp(config.name_case, "lowercase")) {
+            lowercase(window_name);
+        }
+        if (!strcmp(config.name_case, "uppercase")) {
+            uppercase(window_name);
+        }
+
+        char* padded_name = pad_spaces(window_name);
+
+        print_polybar_str(padded_name, window_fg_color, window_bg_color, window_ul_color,
+                          window_left_click, window_middle_click, window_right_click,
+                          window_scroll_up, window_scroll_down);
+
+        window_count++;
+        free(window_nickname);
+        free(padded_name);
+        free(wlist[i].class);
+        free(wlist[i].title);
+    }
+
+    if (window_count == 0) {
+        print_polybar_str(config.empty_desktop_string, config.empty_desktop_fg_color, config.empty_desktop_bg_color, config.empty_desktop_ul_color,
+                          "none", "none", "none", "none", "none");
+    }
+
+    if (window_count > config.max_windows) {
+        char overflow_string[20];
+        snprintf(overflow_string, 20, "(+%d)", window_count - config.max_windows);
+        print_polybar_str(overflow_string, config.overflow_fg_color, config.overflow_bg_color, config.overflow_ul_color,
+                          "none", "none", "none", "none", "none");
+    }
+
+    printf("\n");
+}
+
+void configure_windows_notify(Display* d, struct window_props* prev_wlist, int prev_wlist_len, struct window_props* wlist, int n) {
+    for (int i = 0; i < n; i++) {
+        bool found = false;
+        for (int j = 0; j < prev_wlist_len; j++) {
+            if (wlist[i].id == prev_wlist[j].id) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            XSelectInput(d, wlist[i].id, PropertyChangeMask);
+        }
+    }
+}
+
+int main(int argc, char* argv[]) {
+    Display* d = XOpenDisplay(NULL);
+
+    char* path = dirname(argv[0]);
+    toml_table_t* tbl = parse_config("config.toml", path);
+
+    // Ask X server to send ConfigureNotify and PropertyNotify events for root window
+    // ConfigureNotify is sent when a window's size or position changes
+    // PropertyNotify for changes in client list and active window
+    Window root = DefaultRootWindow(d);
+    XSelectInput(d, root, SubstructureNotifyMask | PropertyChangeMask);
+
+    XEvent e;
+    struct window_props* prev_wlist = NULL;
+    int prev_wlist_len = 0;
+
+    // Listen to XEvents forever and print the window list (output to stdout)
+    for (;;) {
+        fflush(stdout);
+        XNextEvent(d, &e);
+
+        long current_desktop_id = get_desktop_id(d, root, "_NET_CURRENT_DESKTOP");
+        Window active_window = get_active_window(d);
+
+        if (e.type == ConfigureNotify || e.type == PropertyNotify) {
+            int n;
+            struct window_props* wlist = generate_window_list(d, current_desktop_id, &n);
+
+            // Get events for individual windows' property changes,
+            // to know when a window's title (WM_NAME) changes
+            configure_windows_notify(d, prev_wlist, prev_wlist_len, wlist, n);
+
+            output(wlist, n, active_window, path);
+
+            free(prev_wlist);
+            prev_wlist = wlist;
+            prev_wlist_len = n;
+        }
+    }
+    free(prev_wlist);
+
+    toml_free(tbl);
+    XCloseDisplay(d);
+}
diff --git a/home-config/polybar/scripts/windowlist/screenshot.png b/home-config/polybar/scripts/windowlist/screenshot.png
new file mode 100644 (file)
index 0000000..10165cf
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/screenshot.png differ
diff --git a/home-config/polybar/scripts/windowlist/toml-c.h b/home-config/polybar/scripts/windowlist/toml-c.h
new file mode 100644 (file)
index 0000000..a7365a6
--- /dev/null
@@ -0,0 +1,2132 @@
+// TOML config file parsing library
+// https://github.com/arp242/toml-c
+
+#ifndef TOML_H
+#define TOML_H
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#ifdef _MSC_VER
+#pragma warning(disable : 4996)
+#endif
+#ifdef __cplusplus
+#define TOML_EXTERN extern "C"
+#else
+#define TOML_EXTERN extern
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct toml_table_t     toml_table_t;
+typedef struct toml_array_t     toml_array_t;
+typedef struct toml_value_t     toml_value_t;
+typedef struct toml_timestamp_t toml_timestamp_t;
+typedef struct toml_keyval_t    toml_keyval_t;
+typedef struct toml_arritem_t   toml_arritem_t;
+
+// TOML table.
+struct toml_table_t {
+       const char *key;       // Key for this table
+       int keylen;            // length of key.
+       bool implicit;         // Table was created implicitly
+       bool readonly;         // No more modification allowed
+
+       int nkval;             // key-values in the table
+       toml_keyval_t **kval;
+       int narr;              // arrays in the table
+       toml_array_t **arr;
+       int ntab;              // tables in the table
+       toml_table_t **tab;
+};
+
+// TOML array.
+struct toml_array_t {
+       const char *key; // key to this array
+       int keylen;      // length of key.
+       int kind;        // element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed
+       int type;        // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed
+       int nitem;       // number of elements
+       toml_arritem_t *item;
+};
+struct toml_arritem_t {
+       int valtype; // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp
+       char *val;
+       toml_array_t *arr;
+       toml_table_t *tab;
+};
+
+// TOML key/value pair.
+struct toml_keyval_t {
+       const char *key; // key to this value
+       int keylen;      // length of key.
+       const char *val; // the raw value
+};
+
+// Parsed TOML value.
+//
+// The string value s is a regular NULL-terminated C string, but the string
+// length is also given in sl since TOML values may contain NULL bytes. The
+// value is guaranteed to be correct UTF-8.
+struct toml_value_t {
+       bool ok; // Was this value present?
+       union {
+               toml_timestamp_t *ts; // datetime; must be freed after use.
+               char             *s;  // string value; must be freed after use
+               int              sl;  // string length, excluding NULL.
+               bool             b;   // bool value
+               int64_t          i;   // int value
+               double           d;   // double value
+       } u;
+};
+
+// Timestamp type; some values may be empty depending on the value of kind.
+struct toml_timestamp_t {
+       // 'd'atetime
+       // 'l'local-datetime
+       // 'D'ate-local
+       // 't'ime-local
+       char kind;
+       int year, month, day;
+       int hour, minute, second, millisec;
+       char *z;
+};
+
+// toml_parse() parses a TOML document from a string. Returns 0 on error, with
+// the error message stored in errbuf.
+//
+// toml_parse_file() is identical, but reads from a file descriptor.
+//
+// Use toml_free() to free the return value; this will invalidate all handles
+// for this table.
+       TOML_EXTERN toml_table_t *toml_parse      (char *toml, char *errbuf, int errbufsz);
+       TOML_EXTERN toml_table_t *toml_parse_file (FILE *fp, char *errbuf, int errbufsz);
+       TOML_EXTERN void          toml_free       (toml_table_t *table);
+
+// Table functions.
+//
+// toml_table_len() gets the number of direct keys for this table;
+// toml_table_key() gets the nth direct key in this table.
+       TOML_EXTERN int           toml_table_len       (const toml_table_t *table);
+       TOML_EXTERN const char   *toml_table_key       (const toml_table_t *table, int keyidx, int *keylen);
+       TOML_EXTERN toml_value_t  toml_table_string    (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_bool      (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_int       (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_double    (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_timestamp (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_array_t *toml_table_array     (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_table_t *toml_table_table     (const toml_table_t *table, const char *key);
+
+// Array functions.
+       TOML_EXTERN int           toml_array_len       (const toml_array_t *array);
+       TOML_EXTERN toml_value_t  toml_array_string    (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_bool      (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_int       (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_double    (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_timestamp (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_array_t *toml_array_array     (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_table_t *toml_array_table     (const toml_array_t *array, int idx);
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define ALIGN8(sz) (((sz) + 7) & ~7)
+#define calloc(x, y) error - forbidden - use CALLOC instead
+static void *CALLOC(size_t nmemb, size_t sz) {
+       int nb = ALIGN8(sz) * nmemb;
+       void *p = malloc(nb);
+       if (p) {
+               memset(p, 0, nb);
+       }
+       return p;
+}
+
+// some old platforms define strdup macro -- drop it.
+#undef strdup
+#define strdup(x) error - forbidden - use STRDUP instead
+static char *STRDUP(const char *s) {
+       int len = strlen(s);
+       char *p = malloc(len + 1);
+       if (p) {
+               memcpy(p, s, len);
+               p[len] = 0;
+       }
+       return p;
+}
+
+// some old platforms define strndup macro -- drop it.
+#undef strndup
+#define strndup(x) error - forbiden - use STRNDUP instead
+static char *STRNDUP(const char *s, size_t n) {
+       size_t len = strnlen(s, n);
+       char *p = malloc(len + 1);
+       if (p) {
+               memcpy(p, s, len);
+               p[len] = 0;
+       }
+       return p;
+}
+
+// Unparsed values.
+typedef const char *toml_unparsed_t;
+toml_unparsed_t toml_table_unparsed  (const toml_table_t *table, const char *key);
+toml_unparsed_t toml_array_unparsed  (const toml_array_t *array, int idx);
+int             toml_value_string    (toml_unparsed_t s, char **ret, int *len);
+int             toml_value_bool      (toml_unparsed_t s, bool *ret);
+int             toml_value_int       (toml_unparsed_t s, int64_t *ret);
+int             toml_value_double    (toml_unparsed_t s, double *ret);
+int             toml_value_timestamp (toml_unparsed_t s, toml_timestamp_t *ret);
+
+// Convert escape to UTF-8; return #bytes used in buf to encode the char, or -1
+// on error.
+// http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16
+int read_unicode_escape(int64_t code, char buf[6]) {
+       if (0xd800 <= code && code <= 0xdfff) /// UTF-16 surrogates
+               return -1;
+       if (0x10FFFF < code)
+               return -1;
+       if (code < 0)
+               return -1;
+       if (code <= 0x7F) { /// 0x00000000 - 0x0000007F: 0xxxxxxx
+               buf[0] = (unsigned char)code;
+               return 1;
+       }
+       if (code <= 0x000007FF) { /// 0x00000080 - 0x000007FF: 110xxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xc0 | (code >> 6));
+               buf[1] = (unsigned char)(0x80 | (code & 0x3f));
+               return 2;
+       }
+       if (code <= 0x0000FFFF) { /// 0x00000800 - 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xe0 | (code >> 12));
+               buf[1] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | (code & 0x3f));
+               return 3;
+       }
+       if (code <= 0x001FFFFF) { /// 0x00010000 - 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xf0 | (code >> 18));
+               buf[1] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | (code & 0x3f));
+               return 4;
+       }
+       if (code <= 0x03FFFFFF) { /// 0x00200000 - 0x03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xf8 | (code >> 24));
+               buf[1] = (unsigned char)(0x80 | ((code >> 18) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[4] = (unsigned char)(0x80 | (code & 0x3f));
+               return 5;
+       }
+       if (code <= 0x7FFFFFFF) { /// 0x04000000 - 0x7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xfc | (code >> 30));
+               buf[1] = (unsigned char)(0x80 | ((code >> 24) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 18) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[4] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[5] = (unsigned char)(0x80 | (code & 0x3f));
+               return 6;
+       }
+       return -1;
+}
+
+static inline void xfree(const void *x) {
+       if (x)
+               free((void *)(intptr_t)x);
+}
+
+enum tokentype_t {
+       INVALID,
+       DOT,
+       COMMA,
+       EQUAL,
+       LBRACE,
+       RBRACE,
+       NEWLINE,
+       LBRACKET,
+       RBRACKET,
+       STRING,
+};
+typedef enum tokentype_t tokentype_t;
+
+typedef struct token_t token_t;
+struct token_t {
+       tokentype_t tok;
+       int lineno;
+       char *ptr; // points into context->start
+       int len;
+       int eof;
+};
+
+typedef struct context_t context_t;
+struct context_t {
+       char *start;
+       char *stop;
+       char *errbuf;
+       int errbufsz;
+
+       token_t tok;
+       toml_table_t *root;
+       toml_table_t *curtab;
+
+       struct {
+               int top;
+               char *key[10];
+               int keylen[10];
+               token_t tok[10];
+       } tpath;
+};
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define FLINE __FILE__ ":" TOSTRING(__LINE__)
+
+static int next_token(context_t *ctx, bool dotisspecial);
+
+// Error reporting. Call when an error is detected. Always return -1.
+static int e_outofmemory(context_t *ctx, const char *fline) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline);
+       return -1;
+}
+
+static int e_internal(context_t *ctx, const char *fline) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline);
+       return -1;
+}
+
+static int e_syntax(context_t *ctx, int lineno, const char *msg) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+       return -1;
+}
+
+static int e_badkey(context_t *ctx, int lineno) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno);
+       return -1;
+}
+
+static int e_keyexists(context_t *ctx, int lineno) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno);
+       return -1;
+}
+
+static int e_forbid(context_t *ctx, int lineno, const char *msg) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+       return -1;
+}
+
+static void *expand(void *p, int sz, int newsz) {
+       void *s = malloc(newsz);
+       if (!s)
+               return 0;
+
+       if (p) {
+               memcpy(s, p, sz);
+               free(p);
+       }
+       return s;
+}
+
+static void **expand_ptrarr(void **p, int n) {
+       void **s = malloc((n + 1) * sizeof(void *));
+       if (!s)
+               return 0;
+
+       s[n] = 0;
+       if (p) {
+               memcpy(s, p, n * sizeof(void *));
+               free(p);
+       }
+       return s;
+}
+
+static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) {
+       toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p));
+       if (!pp)
+               return 0;
+
+       memset(&pp[n], 0, sizeof(pp[n]));
+       return pp;
+}
+
+static uint8_t const u8_length[] = {1,1,1,1,1,1,1,1,0,0,0,0,2,2,3,4};
+#define u8length(s) u8_length[(((uint8_t *)(s))[0] & 0xFF) >> 4];
+
+static char *norm_lit_str(const char *src, int srclen, int *len, bool multiline, bool is_key, char *errbuf, int errbufsz) {
+       const char *sp  = src;
+       const char *sq  = src + srclen;
+       char       *dst = 0; /// will write to dst[] and return it
+       int        max  = 0; /// max size of dst[]
+       int        off  = 0; /// cur offset in dst[]
+
+       for (;;) { /// scan forward on src
+               if (off >= max - 10) { /// have some slack for misc stuff
+                       int newmax = max + 50;
+                       char *x = expand(dst, max, newmax);
+                       if (!x) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               return 0;
+                       }
+                       dst = x;
+                       max = newmax;
+               }
+
+               if (sp >= sq) /// finished?
+                       break;
+
+               uint8_t l = u8length(sp);
+               if (l == 0) {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                       return 0;
+               }
+               if (l > 1) {
+                       for (int i = 0; i < l; i++) {
+                               char ch = *sp++;
+                               if ((ch & 0x80) != 0x80) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                                       return 0;
+                               }
+                               dst[off++] = ch;
+                       }
+                       continue;
+               }
+
+               char ch = *sp++;
+               if (is_key && ch == '\n') {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "literal newlines not allowed in key");
+                       return 0;
+               }
+               /// control characters other than tab is not allowed
+               if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || ch == 0x7f) {
+                       if (!(multiline && (ch == '\r' || ch == '\n'))) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+                               return 0;
+                       }
+               }
+
+               dst[off++] = ch; /// a plain copy suffice
+       }
+
+       *len = off;
+       dst[off++] = 0;
+       return dst;
+}
+
+/* Convert src to raw unescaped utf-8 string.
+ * Returns NULL if error with errmsg in errbuf. */
+static char *norm_basic_str(const char *src, int srclen, int *len, bool multiline, bool is_key, char *errbuf, int errbufsz) {
+       const char *sp  = src;
+       const char *sq  = src + srclen;
+       char       *dst = 0; /// will write to dst[] and return it
+       int        max  = 0; /// max size of dst[]
+       int        off  = 0; /// cur offset in dst[]
+                                                ///
+       /// scan forward on src
+       for (;;) {
+               if (off >= max - 10) { /// have some slack for misc stuff
+                       int newmax = max + 50;
+                       char *x = expand(dst, max, newmax);
+                       if (!x) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               return 0;
+                       }
+                       dst = x;
+                       max = newmax;
+               }
+
+               if (sp >= sq) /// finished?
+                       break;
+
+               uint8_t l = u8length(sp);
+               if (l == 0) {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                       return 0;
+               }
+               if (l > 1) {
+                       for (int i = 0; i < l; i++) {
+                               char ch = *sp++;
+                               if ((ch & 0x80) != 0x80) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                                       return 0;
+                               }
+                               dst[off++] = ch;
+                       }
+                       continue;
+               }
+
+               char ch = *sp++;
+               if (is_key && ch == '\n') {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "literal newlines not allowed in key");
+                       return 0;
+               }
+               if (ch != '\\') {
+                       /// must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F
+                       if ((ch >= 0 && ch <= 0x08) || (ch >= 0x0a && ch <= 0x1f) || ch == 0x7f) {
+                               if (!(multiline && (ch == '\r' || ch == '\n'))) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+                                       return 0;
+                               }
+                       }
+
+                       dst[off++] = ch; /// a plain copy suffice
+                       continue;
+               }
+
+               if (sp >= sq) { /// ch was backslash. we expect the escape char.
+                       snprintf(errbuf, errbufsz, "last backslash is invalid");
+                       xfree(dst);
+                       return 0;
+               }
+
+               if (multiline) { /// for multi-line, we want to kill line-ending-backslash.
+                       if (sp[strspn(sp, " \t\r")] == '\n') { /// if there is only whitespace after the backslash ...
+                               sp += strspn(sp, " \t\r\n"); /// skip all the following whitespaces
+                               continue;
+                       }
+               }
+
+               ch = *sp++; /// get the escaped char
+               switch (ch) {
+                       case 'u':
+                       case 'U': {
+                               int64_t ucs = 0;
+                               int nhex = (ch == 'u' ? 4 : 8);
+                               for (int i = 0; i < nhex; i++) {
+                                       if (sp >= sq) {
+                                               snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex);
+                                               xfree(dst);
+                                               return 0;
+                                       }
+                                       ch = *sp++;
+                                       int v = -1;
+                                       if ('0' <= ch && ch <= '9')
+                                               v = ch - '0';
+                                       else if ('A' <= ch && ch <= 'F')
+                                               v = ch - 'A' + 10;
+                                       else if ('a' <= ch && ch <= 'f')
+                                               v = (ch ^ 0x20) - 'A' + 10;
+                                       if (v == -1) {
+                                               snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U");
+                                               xfree(dst);
+                                               return 0;
+                                       }
+                                       ucs = ucs * 16 + v;
+                               }
+                               int n = read_unicode_escape(ucs, &dst[off]);
+                               if (n == -1) {
+                                       snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U");
+                                       xfree(dst);
+                                       return 0;
+                               }
+                               off += n;
+                       }; continue;
+                       case 'b':  ch = '\b';  break;
+                       case 't':  ch = '\t';  break;
+                       case 'n':  ch = '\n';  break;
+                       case 'f':  ch = '\f';  break;
+                       case 'r':  ch = '\r';  break;
+                       case '"':  ch = '"';   break;
+                       case '\\': ch = '\\'; break;
+                       default:
+                               snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch);
+                               xfree(dst);
+                               return 0;
+               }
+
+               dst[off++] = ch;
+       }
+
+       *len = off;
+       dst[off++] = 0; /// Cap with NUL and return it.
+       return dst;
+}
+
+// Normalize a key. Convert all special chars to raw unescaped utf-8 chars.
+static char *normalize_key(context_t *ctx, token_t strtok, int *keylen) {
+       const char *sp    = strtok.ptr;
+       const char *sq    = strtok.ptr + strtok.len;
+       int        lineno = strtok.lineno;
+       int        ch     = *sp;
+       char       *ret;
+
+       // Quoted string
+       if (ch == '\'' || ch == '\"') {
+               /// if ''' or """, take 3 chars off front and back. Else, take 1 char off.
+               bool multiline = (sp[1] == ch && sp[2] == ch);
+               if (multiline)
+                       sp += 3, sq -= 3;
+               else
+                       sp++, sq--;
+
+               char ebuf[80];
+               if (ch == '\'')
+                       ret = norm_lit_str(sp, sq - sp, keylen, multiline, true, ebuf, sizeof(ebuf));
+               else
+                       ret = norm_basic_str(sp, sq - sp, keylen, multiline, true, ebuf, sizeof(ebuf));
+               if (!ret) {
+                       e_syntax(ctx, lineno, ebuf);
+                       return 0;
+               }
+               return ret;
+       }
+
+       *keylen = 0;
+       for (const char *c = sp; c != sq; c++) { /// Bare key: allow: [A-Za-z0-9_-]+
+               *keylen = *keylen + 1;
+               if (isalnum(*c) || *c == '_' || *c == '-')
+                       continue;
+               e_badkey(ctx, lineno);
+               return 0;
+       }
+
+       if (!(ret = STRNDUP(sp, sq - sp))) { /// dup and return
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       return ret;
+}
+
+/* Look up key in tab. Return 0 if not found, or
+ * 'v'alue, 'a'rray or 't'able depending on the element. */
+static int check_key(toml_table_t *tab, const char *key, toml_keyval_t **ret_val, toml_array_t **ret_arr, toml_table_t **ret_tab) {
+       int i;
+       void *dummy;
+
+       if (!ret_tab)
+               ret_tab = (toml_table_t **)&dummy;
+       if (!ret_arr)
+               ret_arr = (toml_array_t **)&dummy;
+       if (!ret_val)
+               ret_val = (toml_keyval_t **)&dummy;
+
+       *ret_tab = 0;
+       *ret_arr = 0;
+       *ret_val = 0;
+
+       for (i = 0; i < tab->nkval; i++) {
+               if (strcmp(key, tab->kval[i]->key) == 0) {
+                       *ret_val = tab->kval[i];
+                       return 'v';
+               }
+       }
+       for (i = 0; i < tab->narr; i++) {
+               if (strcmp(key, tab->arr[i]->key) == 0) {
+                       *ret_arr = tab->arr[i];
+                       return 'a';
+               }
+       }
+       for (i = 0; i < tab->ntab; i++) {
+               if (strcmp(key, tab->tab[i]->key) == 0) {
+                       *ret_tab = tab->tab[i];
+                       return 't';
+               }
+       }
+       return 0;
+}
+
+static int key_kind(toml_table_t *tab, const char *key) {
+       return check_key(tab, key, 0, 0, 0);
+}
+
+/* Create a keyval in the table. */
+static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab, token_t keytok) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       toml_keyval_t *dest = 0;
+       if (key_kind(tab, newkey)) {
+               xfree(newkey);
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->nkval;
+       toml_keyval_t **base;
+       if ((base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->kval = base;
+
+       if ((base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+
+       dest = tab->kval[tab->nkval++];
+       dest->key = newkey;
+       dest->keylen = keylen;
+       return dest;
+}
+
+// Create a table in the table.
+static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab, token_t keytok) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       toml_table_t *dest = 0;
+       if (check_key(tab, newkey, 0, 0, &dest)) {
+               xfree(newkey);
+
+               /// Special case: make explicit if table exists and was created
+               /// implicitly.
+               if (dest && dest->implicit) {
+                       dest->implicit = false;
+                       return dest;
+               }
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->ntab;
+       toml_table_t **base;
+       if ((base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->tab = base;
+
+       if ((base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+
+       dest = tab->tab[tab->ntab++];
+       dest->key = newkey;
+       dest->keylen = keylen;
+       return dest;
+}
+
+// Create an array in the table.
+static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab, token_t keytok, char kind) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       if (key_kind(tab, newkey)) {
+               xfree(newkey);
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->narr;
+       toml_array_t **base;
+       if ((base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->arr = base;
+
+       if ((base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_array_t *dest = tab->arr[tab->narr++];
+
+       dest->keylen = keylen;
+       dest->key = newkey;
+       dest->kind = kind;
+       return dest;
+}
+
+static toml_arritem_t *create_value_in_array(context_t *ctx, toml_array_t *parent) {
+       const int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       parent->item = base;
+       parent->nitem++;
+       return &parent->item[n];
+}
+
+/* Create an array in an array */
+static toml_array_t *create_array_in_array(context_t *ctx,
+               toml_array_t *parent) {
+       const int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t));
+       if (!ret) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       base[n].arr = ret;
+       parent->item = base;
+       parent->nitem++;
+       return ret;
+}
+
+/* Create a table in an array */
+static toml_table_t *create_table_in_array(context_t *ctx, toml_array_t *parent) {
+       int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t));
+       if (!ret) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       base[n].tab = ret;
+       parent->item = base;
+       parent->nitem++;
+       return ret;
+}
+
+static int skip_newlines(context_t *ctx, bool isdotspecial) {
+       while (ctx->tok.tok == NEWLINE) {
+               if (next_token(ctx, isdotspecial))
+                       return -1;
+               if (ctx->tok.eof)
+                       break;
+       }
+       return 0;
+}
+
+static int parse_keyval(context_t *ctx, toml_table_t *tab);
+
+static inline int eat_token(context_t *ctx, tokentype_t typ, bool isdotspecial, const char *fline) {
+       if (ctx->tok.tok != typ)
+               return e_internal(ctx, fline);
+
+       if (next_token(ctx, isdotspecial))
+               return -1;
+
+       return 0;
+}
+
+/* We are at '{ ... }'; parse the table. */
+static int parse_inline_table(context_t *ctx, toml_table_t *tab) {
+       if (eat_token(ctx, LBRACE, 1, FLINE))
+               return -1;
+
+       for (;;) {
+               if (ctx->tok.tok == NEWLINE)
+                       return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+               if (ctx->tok.tok == RBRACE) // until closing brace
+                       break;
+
+               if (ctx->tok.tok != STRING)
+                       return e_syntax(ctx, ctx->tok.lineno, "expect a string");
+
+               if (parse_keyval(ctx, tab))
+                       return -1;
+
+               if (ctx->tok.tok == NEWLINE)
+                       return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+               /* on comma, continue to scan for next keyval */
+               if (ctx->tok.tok == COMMA) {
+                       if (eat_token(ctx, COMMA, 1, FLINE))
+                               return -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (eat_token(ctx, RBRACE, 1, FLINE))
+               return -1;
+       tab->readonly = 1;
+       return 0;
+}
+
+static int valtype(const char *val) {
+       toml_timestamp_t ts;
+       if (*val == '\'' || *val == '"')
+               return 's';
+       if (toml_value_bool(val, false) == 0)
+               return 'b';
+       if (toml_value_int(val, 0) == 0)
+               return 'i';
+       if (toml_value_double(val, 0) == 0)
+               return 'd';
+       if (toml_value_timestamp(val, &ts) == 0) {
+               if (ts.year && ts.hour)
+                       return 'T'; /// timestamp
+               if (ts.year)
+                       return 'D'; /// date
+               return 't';   /// time
+       }
+       return 'u'; /// unknown
+}
+
+/* We are at '[...]' */
+static int parse_array(context_t *ctx, toml_array_t *arr) {
+       if (eat_token(ctx, LBRACKET, 0, FLINE))
+               return -1;
+
+       for (;;) {
+               if (skip_newlines(ctx, 0))
+                       return -1;
+
+               if (ctx->tok.tok == RBRACKET) /// until ]
+                       break;
+
+               switch (ctx->tok.tok) {
+                       case STRING: {
+                               /// set array kind if this will be the first entry
+                               if (arr->kind == 0)
+                                       arr->kind = 'v';
+                               else if (arr->kind != 'v')
+                                       arr->kind = 'm';
+
+                               char *val = ctx->tok.ptr;
+                               int vlen = ctx->tok.len;
+
+                               /// make a new value in array
+                               toml_arritem_t *newval = create_value_in_array(ctx, arr);
+                               if (!newval)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               if (!(newval->val = STRNDUP(val, vlen)))
+                                       return e_outofmemory(ctx, FLINE);
+
+                               newval->valtype = valtype(newval->val);
+
+                               /// set array type if this is the first entry
+                               if (arr->nitem == 1)
+                                       arr->type = newval->valtype;
+                               else if (arr->type != newval->valtype)
+                                       arr->type = 'm'; /// mixed
+
+                               if (eat_token(ctx, ctx->tok.tok, 0, FLINE))
+                                       return -1;
+                               break;
+                       }
+                       case LBRACKET: { /* [ [array], [array] ... ] */
+                               /* set the array kind if this will be the first entry */
+                               if (arr->kind == 0)
+                                       arr->kind = 'a';
+                               else if (arr->kind != 'a')
+                                       arr->kind = 'm';
+
+                               toml_array_t *subarr = create_array_in_array(ctx, arr);
+                               if (!subarr)
+                                       return -1;
+                               if (parse_array(ctx, subarr))
+                                       return -1;
+                               break;
+                       }
+                       case LBRACE: { /* [ {table}, {table} ... ] */
+                               /* set the array kind if this will be the first entry */
+                               if (arr->kind == 0)
+                                       arr->kind = 't';
+                               else if (arr->kind != 't')
+                                       arr->kind = 'm';
+
+                               toml_table_t *subtab = create_table_in_array(ctx, arr);
+                               if (!subtab)
+                                       return -1;
+                               if (parse_inline_table(ctx, subtab))
+                                       return -1;
+                               break;
+                       }
+                       default:
+                               return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+               }
+
+               if (skip_newlines(ctx, 0))
+                       return -1;
+
+               /* on comma, continue to scan for next element */
+               if (ctx->tok.tok == COMMA) {
+                       if (eat_token(ctx, COMMA, 0, FLINE))
+                               return -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (eat_token(ctx, RBRACKET, 1, FLINE))
+               return -1;
+       return 0;
+}
+
+/* handle lines like these:
+   key = "value"
+   key = [ array ]
+   key = { table } */
+static int parse_keyval(context_t *ctx, toml_table_t *tab) {
+       if (tab->readonly) {
+               return e_forbid(ctx, ctx->tok.lineno, "cannot insert new entry into existing table");
+       }
+
+       token_t key = ctx->tok;
+       if (eat_token(ctx, STRING, 1, FLINE))
+               return -1;
+
+       if (ctx->tok.tok == DOT) {
+               /* handle inline dotted key. e.g.
+                  physical.color = "orange"
+                  physical.shape = "round" */
+               toml_table_t *subtab = 0;
+               {
+                       int keylen;
+                       char *subtabstr = normalize_key(ctx, key, &keylen);
+                       if (!subtabstr)
+                               return -1;
+
+                       subtab = toml_table_table(tab, subtabstr);
+                       if (subtab)
+                               subtab->keylen = keylen;
+                       xfree(subtabstr);
+               }
+               if (!subtab) {
+                       subtab = create_keytable_in_table(ctx, tab, key);
+                       if (!subtab)
+                               return -1;
+               }
+               if (next_token(ctx, true))
+                       return -1;
+               if (parse_keyval(ctx, subtab))
+                       return -1;
+               return 0;
+       }
+
+       if (ctx->tok.tok != EQUAL)
+               return e_syntax(ctx, ctx->tok.lineno, "missing =");
+
+       if (next_token(ctx, false))
+               return -1;
+
+       switch (ctx->tok.tok) {
+               case STRING: { // key = "value"
+                       toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key);
+                       if (!keyval)
+                               return -1;
+                       token_t val = ctx->tok;
+
+                       assert(keyval->val == 0);
+                       if (!(keyval->val = STRNDUP(val.ptr, val.len)))
+                               return e_outofmemory(ctx, FLINE);
+
+                       if (next_token(ctx, true))
+                               return -1;
+
+                       return 0;
+               }
+               case LBRACKET: { /* key = [ array ] */
+                       toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0);
+                       if (!arr)
+                               return -1;
+                       if (parse_array(ctx, arr))
+                               return -1;
+                       return 0;
+               }
+               case LBRACE: { /* key = { table } */
+                       toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key);
+                       if (!nxttab)
+                               return -1;
+                       if (parse_inline_table(ctx, nxttab))
+                               return -1;
+                       return 0;
+               }
+               default:
+                       return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+       }
+       return 0;
+}
+
+typedef struct tabpath_t tabpath_t;
+struct tabpath_t {
+       int cnt;
+       token_t key[10];
+};
+
+/* at [x.y.z] or [[x.y.z]]
+ * Scan forward and fill tabpath until it enters ] or ]]
+ * There will be at least one entry on return. */
+static int fill_tabpath(context_t *ctx) {
+       // clear tpath
+       for (int i = 0; i < ctx->tpath.top; i++) {
+               char **p = &ctx->tpath.key[i];
+               xfree(*p);
+               *p = 0;
+       }
+       ctx->tpath.top = 0;
+
+       for (;;) {
+               if (ctx->tpath.top >= 10)
+                       return e_syntax(ctx, ctx->tok.lineno, "table path is too deep; max allowed is 10.");
+               if (ctx->tok.tok != STRING)
+                       return e_syntax(ctx, ctx->tok.lineno, "invalid or missing key");
+
+               int keylen;
+               char *key = normalize_key(ctx, ctx->tok, &keylen);
+               if (!key)
+                       return -1;
+               ctx->tpath.tok[ctx->tpath.top] = ctx->tok;
+               ctx->tpath.key[ctx->tpath.top] = key;
+               ctx->tpath.keylen[ctx->tpath.top] = keylen;
+               ctx->tpath.top++;
+
+               if (next_token(ctx, true))
+                       return -1;
+
+               if (ctx->tok.tok == RBRACKET)
+                       break;
+               if (ctx->tok.tok != DOT)
+                       return e_syntax(ctx, ctx->tok.lineno, "invalid key");
+               if (next_token(ctx, true))
+                       return -1;
+       }
+
+       if (ctx->tpath.top <= 0)
+               return e_syntax(ctx, ctx->tok.lineno, "empty table selector");
+       return 0;
+}
+
+/* Walk tabpath from the root, and create new tables on the way.
+ * Sets ctx->curtab to the final table. */
+static int walk_tabpath(context_t *ctx) {
+       toml_table_t *curtab = ctx->root; /// start from root
+
+       for (int i = 0; i < ctx->tpath.top; i++) {
+               const char *key = ctx->tpath.key[i];
+               int keylen = ctx->tpath.keylen[i];
+
+               toml_keyval_t *nextval = 0;
+               toml_array_t *nextarr = 0;
+               toml_table_t *nexttab = 0;
+               switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) {
+                       case 't': /// found a table. nexttab is where we will go next.
+                               break;
+                       case 'a': /// found an array. nexttab is the last table in the array.
+                               if (nextarr->kind != 't')
+                                       return e_internal(ctx, FLINE);
+
+                               if (nextarr->nitem == 0)
+                                       return e_internal(ctx, FLINE);
+
+                               nexttab = nextarr->item[nextarr->nitem - 1].tab;
+                               break;
+                       case 'v':
+                               return e_keyexists(ctx, ctx->tpath.tok[i].lineno);
+                       default: { /// Not found. Let's create an implicit table.
+                               int n = curtab->ntab;
+                               toml_table_t **base = (toml_table_t **)expand_ptrarr((void **)curtab->tab, n);
+                               if (base == 0)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               curtab->tab = base;
+
+                               if ((base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n]))) == 0)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               if ((base[n]->key = STRDUP(key)) == 0)
+                                       return e_outofmemory(ctx, FLINE);
+                               base[n]->keylen = keylen;
+
+                               nexttab = curtab->tab[curtab->ntab++];
+
+                               /// tabs created by walk_tabpath are considered implicit
+                               nexttab->implicit = true;
+                       }; break;
+               }
+               curtab = nexttab; /// switch to next tab
+       }
+
+       ctx->curtab = curtab; /// save it
+       return 0;
+}
+
+/* handle lines like [x.y.z] or [[x.y.z]] */
+static int parse_select(context_t *ctx) {
+       assert(ctx->tok.tok == LBRACKET);
+
+       /* true if [[ */
+       int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '[');
+       /* need to detect '[[' on our own because next_token() will skip whitespace,
+          and '[ [' would be taken as '[[', which is wrong. */
+
+       /* eat [ or [[ */
+       if (eat_token(ctx, LBRACKET, 1, FLINE))
+               return -1;
+       if (llb) {
+               assert(ctx->tok.tok == LBRACKET);
+               if (eat_token(ctx, LBRACKET, 1, FLINE))
+                       return -1;
+       }
+
+       if (fill_tabpath(ctx))
+               return -1;
+
+       /* For [x.y.z] or [[x.y.z]], remove z from tpath. */
+       token_t z = ctx->tpath.tok[ctx->tpath.top - 1];
+       xfree(ctx->tpath.key[ctx->tpath.top - 1]);
+       ctx->tpath.top--;
+
+       /* set up ctx->curtab */
+       if (walk_tabpath(ctx))
+               return -1;
+
+       if (!llb) {
+               /* [x.y.z] -> create z = {} in x.y */
+               toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z);
+               if (!curtab)
+                       return -1;
+               ctx->curtab = curtab;
+       } else {
+               /* [[x.y.z]] -> create z = [] in x.y */
+               toml_array_t *arr = 0;
+               {
+                       int keylen;
+                       char *zstr = normalize_key(ctx, z, &keylen);
+                       if (!zstr)
+                               return -1;
+                       arr = toml_table_array(ctx->curtab, zstr);
+                       if (arr)
+                               arr->keylen = keylen;
+                       xfree(zstr);
+               }
+               if (!arr) {
+                       arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't');
+                       if (!arr)
+                               return -1;
+               }
+               if (arr->kind != 't')
+                       return e_syntax(ctx, z.lineno, "array mismatch");
+
+               /* add to z[] */
+               toml_table_t *dest;
+               {
+                       toml_table_t *t = create_table_in_array(ctx, arr);
+                       if (!t)
+                               return -1;
+
+                       if ((t->key = STRDUP("__anon__")) == 0)
+                               return e_outofmemory(ctx, FLINE);
+
+                       dest = t;
+               }
+
+               ctx->curtab = dest;
+       }
+
+       if (ctx->tok.tok != RBRACKET) {
+               return e_syntax(ctx, ctx->tok.lineno, "expects ]");
+       }
+       if (llb) {
+               if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) {
+                       return e_syntax(ctx, ctx->tok.lineno, "expects ]]");
+               }
+               if (eat_token(ctx, RBRACKET, 1, FLINE))
+                       return -1;
+       }
+
+       if (eat_token(ctx, RBRACKET, 1, FLINE))
+               return -1;
+       if (ctx->tok.tok != NEWLINE)
+               return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]");
+       return 0;
+}
+
+toml_table_t *toml_parse(char *toml, char *errbuf, int errbufsz) {
+       context_t ctx;
+
+       /// clear errbuf
+       if (errbufsz <= 0)
+               errbufsz = 0;
+       if (errbufsz > 0)
+               errbuf[0] = 0;
+
+       // init context
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.start = toml;
+       ctx.stop = ctx.start + strlen(toml);
+       ctx.errbuf = errbuf;
+       ctx.errbufsz = errbufsz;
+
+       // start with an artificial newline of length 0
+       ctx.tok.tok = NEWLINE;
+       ctx.tok.lineno = 1;
+       ctx.tok.ptr = toml;
+       ctx.tok.len = 0;
+
+       // make a root table
+       if ((ctx.root = CALLOC(1, sizeof(*ctx.root))) == 0) {
+               e_outofmemory(&ctx, FLINE);
+               return 0; // Do not goto fail, root table not set up yet
+       }
+
+       // set root as default table
+       ctx.curtab = ctx.root;
+
+       // Scan forward until EOF
+       for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) {
+               switch (tok.tok) {
+                       case NEWLINE:
+                               if (next_token(&ctx, true))
+                                       goto fail;
+                               break;
+
+                       case STRING:
+                               if (parse_keyval(&ctx, ctx.curtab))
+                                       goto fail;
+
+                               if (ctx.tok.tok != NEWLINE) {
+                                       e_syntax(&ctx, ctx.tok.lineno, "extra chars after value");
+                                       goto fail;
+                               }
+
+                               if (eat_token(&ctx, NEWLINE, 1, FLINE))
+                                       goto fail;
+                               break;
+
+                       case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */
+                               if (parse_select(&ctx))
+                                       goto fail;
+                               break;
+
+                       default:
+                               e_syntax(&ctx, tok.lineno, "syntax error");
+                               goto fail;
+               }
+       }
+
+       /// success
+       for (int i = 0; i < ctx.tpath.top; i++)
+               xfree(ctx.tpath.key[i]);
+       return ctx.root;
+
+fail:
+       // Something bad has happened. Free resources and return error.
+       for (int i = 0; i < ctx.tpath.top; i++)
+               xfree(ctx.tpath.key[i]);
+       toml_free(ctx.root);
+       return 0;
+}
+
+toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) {
+       int bufsz = 0;
+       char *buf = 0;
+       int off = 0;
+       int inc = 1024;
+
+       while (!feof(fp)) {
+               if (bufsz == 1024 * 20) /// Increment buffer by 20k after 20k.
+                       inc = 1024 * 20;
+               if (off == bufsz) {
+                       int xsz = bufsz + inc;
+                       char *x = expand(buf, bufsz, xsz);
+                       if (!x) {
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               xfree(buf);
+                               return 0;
+                       }
+                       buf = x;
+                       bufsz = xsz;
+               }
+
+               errno = 0;
+               int n = fread(buf + off, 1, bufsz - off, fp);
+               if (ferror(fp)) {
+                       snprintf(errbuf, errbufsz, "%s", (errno ? strerror(errno) : "Error reading file"));
+                       xfree(buf);
+                       return 0;
+               }
+               off += n;
+       }
+
+       /// tag on a NUL to cap the string
+       if (off == bufsz) {
+               int xsz = bufsz + 1;
+               char *x = expand(buf, bufsz, xsz);
+               if (!x) {
+                       snprintf(errbuf, errbufsz, "out of memory");
+                       xfree(buf);
+                       return 0;
+               }
+               buf = x;
+               bufsz = xsz;
+       }
+       buf[off] = 0;
+
+       /// parse it, cleanup and finish.
+       toml_table_t *ret = toml_parse(buf, errbuf, errbufsz);
+       xfree(buf);
+       return ret;
+}
+
+static void xfree_kval(toml_keyval_t *p) {
+       if (!p)
+               return;
+       xfree(p->key);
+       xfree(p->val);
+       xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p);
+
+static void xfree_arr(toml_array_t *p) {
+       if (!p)
+               return;
+
+       xfree(p->key);
+       const int n = p->nitem;
+       for (int i = 0; i < n; i++) {
+               toml_arritem_t *a = &p->item[i];
+               if (a->val)
+                       xfree(a->val);
+               else if (a->arr)
+                       xfree_arr(a->arr);
+               else if (a->tab)
+                       xfree_tab(a->tab);
+       }
+       xfree(p->item);
+       xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p) {
+       if (!p)
+               return;
+
+       xfree(p->key);
+
+       for (int i = 0; i < p->nkval; i++)
+               xfree_kval(p->kval[i]);
+       xfree(p->kval);
+
+       for (int i = 0; i < p->narr; i++)
+               xfree_arr(p->arr[i]);
+       xfree(p->arr);
+
+       for (int i = 0; i < p->ntab; i++)
+               xfree_tab(p->tab[i]);
+       xfree(p->tab);
+
+       xfree(p);
+}
+
+void toml_free(toml_table_t *tab) { xfree_tab(tab); }
+
+static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr, int len) {
+       token_t t;
+       t.tok    = tok;
+       t.lineno = lineno;
+       t.ptr    = ptr;
+       t.len    = len;
+       t.eof    = 0;
+       ctx->tok = t;
+}
+
+static void set_eof(context_t *ctx, int lineno) {
+       set_token(ctx, NEWLINE, lineno, ctx->stop, 0);
+       ctx->tok.eof = 1;
+}
+
+/* Scan p for n digits compositing entirely of [0-9] */
+static int scan_digits(const char *p, int n) {
+       int ret = 0;
+       for (; n > 0 && isdigit(*p); n--, p++) {
+               ret = 10 * ret + (*p - '0');
+       }
+       return n ? -1 : ret;
+}
+
+static int scan_date(const char *p, int *YY, int *MM, int *DD) {
+       int year, month, day;
+       year = scan_digits(p, 4);
+       month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1;
+       day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1;
+       if (YY)
+               *YY = year;
+       if (MM)
+               *MM = month;
+       if (DD)
+               *DD = day;
+       return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1;
+}
+
+static int scan_time(const char *p, int *hh, int *mm, int *ss) {
+       int hour = scan_digits(p, 2);
+       int minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1;
+       int second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1;
+       if (hh)
+               *hh = hour;
+       if (mm)
+               *mm = minute;
+       if (ss)
+               *ss = second;
+       return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1;
+}
+
+static int scan_string(context_t *ctx, char *p, int lineno, bool dotisspecial) {
+       char *orig = p;
+
+       // Literal multiline.
+       if (strncmp(p, "'''", 3) == 0) {
+               char *q = p + 3;
+               while (true) {
+                       q = strstr(q, "'''");
+                       if (q == 0)
+                               return e_syntax(ctx, lineno, "unterminated triple-s-quote");
+                       int i = 0;
+                       while (q[3] == '\'') {
+                               i++;
+                               if (i >= 3)
+                                       return e_syntax(ctx, lineno, "too many ''' in triple-s-quote");
+                               q++;
+                       }
+                       break;
+               }
+               set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+               return 0;
+       }
+
+       // Multiline.
+       if (strncmp(p, "\"\"\"", 3) == 0) {
+               char *q = p + 3;
+               while (true) {
+                       q = strstr(q, "\"\"\"");
+                       if (q == 0)
+                               return e_syntax(ctx, lineno, "unterminated triple-d-quote");
+                       if (q[-1] == '\\') {
+                               q++;
+                               continue;
+                       }
+                       int i = 0;
+                       while (q[3] == '\"') {
+                               i++;
+                               if (i >= 3)
+                                       return e_syntax(ctx, lineno, "too many \"\"\" in triple-d-quote");
+                               q++;
+                       }
+                       break;
+               }
+
+               /// the string is [p+3, q-1]
+               int hexreq = 0; /// #hex required
+               bool escape = false;
+               for (p += 3; p < q; p++) {
+                       if (escape) {
+                               escape = false;
+                               if (strchr("btnfr\"\\", *p))
+                                       continue;
+                               if (*p == 'u') {
+                                       hexreq = 4;
+                                       continue;
+                               }
+                               if (*p == 'U') {
+                                       hexreq = 8;
+                                       continue;
+                               }
+                               if (p[strspn(p, " \t\r")] == '\n')
+                                       continue; /* allow for line ending backslash */
+                               return e_syntax(ctx, lineno, "bad escape char");
+                       }
+                       if (hexreq) {
+                               hexreq--;
+                               if (strchr("0123456789ABCDEFabcdef", *p))
+                                       continue;
+                               return e_syntax(ctx, lineno, "expect hex char");
+                       }
+                       if (*p == '\\') {
+                               escape = true;
+                               continue;
+                       }
+               }
+               if (escape)
+                       return e_syntax(ctx, lineno, "expect an escape char");
+               if (hexreq)
+                       return e_syntax(ctx, lineno, "expected more hex char");
+
+               set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+               return 0;
+       }
+
+       // Literal string.
+       if (*p == '\'') {
+               for (p++; *p && *p != '\n' && *p != '\''; p++)
+                       ;
+               if (*p != '\'')
+                       return e_syntax(ctx, lineno, "unterminated s-quote");
+
+               set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+               return 0;
+       }
+
+       // Basic String.
+       if (*p == '\"') {
+               int hexreq = 0; /// #hex required
+               bool escape = false;
+               for (p++; *p; p++) {
+                       if (escape) {
+                               escape = false;
+                               if (strchr("btnfr\"\\", *p))
+                                       continue;
+                               if (*p == 'u') {
+                                       hexreq = 4;
+                                       continue;
+                               }
+                               if (*p == 'U') {
+                                       hexreq = 8;
+                                       continue;
+                               }
+                               return e_syntax(ctx, lineno, "bad escape char");
+                       }
+                       if (hexreq) {
+                               hexreq--;
+                               if (strchr("0123456789ABCDEFabcdef", *p))
+                                       continue;
+                               return e_syntax(ctx, lineno, "expect hex char");
+                       }
+                       if (*p == '\\') {
+                               escape = true;
+                               continue;
+                       }
+                       if (*p == '\n')
+                               break;
+                       if (*p == '"')
+                               break;
+               }
+               if (*p != '"')
+                       return e_syntax(ctx, lineno, "unterminated quote");
+
+               set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+               return 0;
+       }
+
+       // Datetime.
+       if (scan_date(p, 0, 0, 0) == 0 || scan_time(p, 0, 0, 0) == 0) {
+               p += strspn(p, "0123456789.:+-Tt Zz"); /// forward thru the timestamp
+               for (; p[-1] == ' '; p--) /// squeeze out any spaces at end of string
+                       ;
+               set_token(ctx, STRING, lineno, orig, p - orig); /// tokenize
+               return 0;
+       }
+
+       // literals
+       for (; *p && *p != '\n'; p++) {
+               int ch = *p;
+               if (ch == '.' && dotisspecial)
+                       break;
+               if ('A' <= ch && ch <= 'Z')
+                       continue;
+               if ('a' <= ch && ch <= 'z')
+                       continue;
+               if (strchr("0123456789+-_.", ch))
+                       continue;
+               break;
+       }
+
+       set_token(ctx, STRING, lineno, orig, p - orig);
+       return 0;
+}
+
+static int next_token(context_t *ctx, bool dotisspecial) {
+       // Eat this tok.
+       char *p = ctx->tok.ptr;
+       int lineno = ctx->tok.lineno;
+       for (int i = 0; i < ctx->tok.len; i++)
+               if (*p++ == '\n')
+                       lineno++;
+
+       /// Make next tok
+       while (p < ctx->stop) {
+               if (*p == '#') { /// Skip comment. stop just before the \n.
+                       for (p++; p < ctx->stop && *p != '\n'; p++)
+                               ;
+                       continue;
+               }
+
+               if (dotisspecial && *p == '.') {
+                       set_token(ctx, DOT, lineno, p, 1);
+                       return 0;
+               }
+
+               switch (*p) {
+                       case ',':
+                               set_token(ctx, COMMA, lineno, p, 1);
+                               return 0;
+                       case '=':
+                               set_token(ctx, EQUAL, lineno, p, 1);
+                               return 0;
+                       case '{':
+                               set_token(ctx, LBRACE, lineno, p, 1);
+                               return 0;
+                       case '}':
+                               set_token(ctx, RBRACE, lineno, p, 1);
+                               return 0;
+                       case '[':
+                               set_token(ctx, LBRACKET, lineno, p, 1);
+                               return 0;
+                       case ']':
+                               set_token(ctx, RBRACKET, lineno, p, 1);
+                               return 0;
+                       case '\n':
+                               set_token(ctx, NEWLINE, lineno, p, 1);
+                               return 0;
+                       case '\r': case ' ': case '\t': /// ignore white spaces
+                               p++;
+                               continue;
+               }
+
+               return scan_string(ctx, p, lineno, dotisspecial);
+       }
+
+       set_eof(ctx, lineno);
+       return 0;
+}
+
+const char *toml_table_key(const toml_table_t *tab, int keyidx, int *keylen) {
+       if (keyidx < tab->nkval) {
+               *keylen = tab->kval[keyidx]->keylen;
+               return    tab->kval[keyidx]->key;
+       }
+       if ((keyidx -= tab->nkval) < tab->narr) {
+               *keylen = tab->arr[keyidx]->keylen;
+               return    tab->arr[keyidx]->key;
+       }
+       if ((keyidx -= tab->narr) < tab->ntab) {
+               *keylen = tab->tab[keyidx]->keylen;
+               return    tab->tab[keyidx]->key;
+       }
+       *keylen = 0;
+       return 0;
+}
+
+toml_unparsed_t toml_table_unparsed(const toml_table_t *tab, const char *key) {
+       for (int i = 0; i < tab->nkval; i++)
+               if (strcmp(key, tab->kval[i]->key) == 0)
+                       return tab->kval[i]->val;
+       return 0;
+}
+
+toml_array_t *toml_table_array(const toml_table_t *tab, const char *key) {
+       for (int i = 0; i < tab->narr; i++)
+               if (strcmp(key, tab->arr[i]->key) == 0)
+                       return tab->arr[i];
+       return 0;
+}
+
+toml_table_t *toml_table_table(const toml_table_t *tab, const char *key) {
+       for (int i  = 0; i < tab->ntab; i++)
+               if (strcmp(key, tab->tab[i]->key) == 0)
+                       return tab->tab[i];
+       return 0;
+}
+
+toml_unparsed_t toml_array_unparsed(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0;
+}
+
+int toml_table_len(const toml_table_t *tbl) {
+       return tbl->nkval + tbl->narr + tbl->ntab;
+}
+
+int toml_array_len(const toml_array_t *arr) {
+       return arr->nitem;
+}
+
+toml_array_t *toml_array_array(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0;
+}
+
+toml_table_t *toml_array_table(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0;
+}
+
+static int parse_millisec(const char *p, const char **endp);
+
+bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); }
+
+int toml_value_timestamp(toml_unparsed_t src_, toml_timestamp_t *ret) {
+       if (!src_)
+               return -1;
+
+       const char *p = src_;
+       bool must_parse_time = false;
+
+       memset(ret, 0, sizeof(*ret));
+
+       /// YYYY-MM-DD
+       if (scan_date(p, &ret->year, &ret->month, &ret->day) == 0) {
+               if (ret->month < 1 || ret->day < 1 || ret->month > 12 || ret->day > 31)
+                       return -1;
+               if (ret->month == 2 && ret->day > (is_leap(ret->year) ? 29 : 28))
+                       return -1;
+               ret->kind = 'D';
+
+               p += 10;
+               if (*p) {
+                       if (*p != 'T' && *p != 't' && *p != ' ') /// T or space
+                               return -1;
+                       must_parse_time = true;
+                       p++;
+               }
+       }
+
+       /// HH:MM:SS
+       if (scan_time(p, &ret->hour, &ret->minute, &ret->second) == 0) {
+               if (ret->second < 0 || ret->minute < 0 || ret->hour < 0 || ret->hour > 23 || ret->minute > 59 || ret->second > 60)
+                       return -1;
+               ret->kind = (ret->kind == 'D' ? 'l' : 't');
+
+               p += 8;
+               if (*p == '.') { /// optionally, parse millisec
+                       p++; /// skip '.'
+                       const char *qq;
+                       ret->millisec = parse_millisec(p, &qq);
+                       p = qq;
+               }
+
+               if (*p) { /// parse and copy Z
+                       ret->kind = 'd';
+                       char *z = malloc(10);
+                       ret->z = z;
+                       if (*p == 'Z' || *p == 'z') {
+                               *z++ = 'Z';
+                               p++;
+                               *z = 0;
+                       } else if (*p == '+' || *p == '-') {
+                               *z++ = *p++;
+
+                               if (!(isdigit(p[0]) && isdigit(p[1])))
+                                       return -1;
+                               *z++ = *p++;
+                               *z++ = *p++;
+
+                               if (*p == ':') {
+                                       *z++ = *p++;
+                                       if (!(isdigit(p[0]) && isdigit(p[1])))
+                                               return -1;
+                                       *z++ = *p++;
+                                       *z++ = *p++;
+                               }
+
+                               *z = 0;
+                       }
+               }
+       }
+       if (*p != 0)
+               return -1;
+       if (must_parse_time && ret->kind == 'D')
+               return -1;
+       return 0;
+}
+
+/* Raw to boolean */
+int toml_value_bool(toml_unparsed_t src, bool *ret_) {
+       if (!src)
+               return -1;
+       bool dummy;
+       bool *ret = ret_ ? ret_ : &dummy;
+
+       if (strcmp(src, "true") == 0) {
+               *ret = true;
+               return 0;
+       }
+       if (strcmp(src, "false") == 0) {
+               *ret = false;
+               return 0;
+       }
+       return -1;
+}
+
+/* Raw to integer */
+int toml_value_int(toml_unparsed_t src, int64_t *ret_) {
+       if (!src)
+               return -1;
+
+       char buf[100];
+       char *p = buf;
+       char *q = p + sizeof(buf);
+       const char *s = src;
+       int base = 0;
+       int64_t dummy;
+       int64_t *ret = ret_ ? ret_ : &dummy;
+       bool have_sign = false;
+
+       if (s[0] == '+' || s[0] == '-') { /// allow +/-
+               have_sign = true;
+               *p++ = *s++;
+       }
+
+       if (s[0] == '_') /// disallow +_100
+               return -1;
+
+       if (s[0] == '0') { /// if 0* ...
+               switch (s[1]) {
+                       case 'x': base = 16; s += 2; break;
+                       case 'o': base = 8;  s += 2; break;
+                       case 'b': base = 2;  s += 2; break;
+                       case '\0':
+                               return *ret = 0, 0;
+                       default:
+                               if (s[1]) /// ensure no other digits after it
+                                       return -1;
+               }
+               if (!*s)
+                       return -1;
+               if (have_sign)   /// disallow +0xff, -0xff
+                       return -1;
+               if (s[0] == '_') /// disallow 0x_, 0o_, 0b_
+                       return -1;
+       }
+
+       while (*s && p < q) { /// just strip underscores and pass to strtoll
+               int ch = *s++;
+               if (ch == '_') {
+                       if (s[0] == '_')  /// disallow '__'
+                               return -1;
+                       if (s[0] == '\0') /// numbers cannot end with '_'
+                               return -1;
+                       continue; /// skip _
+               }
+               *p++ = ch;
+       }
+
+       if (*s || p == q) /// if not at end-of-string or we ran out of buffer ...
+               return -1;
+
+       *p = 0; /// cap with NUL
+
+       /// Run strtoll on buf to get the integer
+       char *endp;
+       errno = 0;
+       *ret = strtoll(buf, &endp, base);
+       return (errno || *endp) ? -1 : 0;
+}
+
+int toml_value_double(toml_unparsed_t src, double *ret_) {
+       if (!src)
+               return -1;
+
+       char buf[100];
+       char *p = buf;
+       char *q = p + sizeof(buf);
+       const char *s = src;
+       double dummy;
+       double *ret = ret_ ? ret_ : &dummy;
+       bool have_us = false;
+
+       if (s[0] == '+' || s[0] == '-') /// allow +/-
+               *p++ = *s++;
+
+       if (s[0] == '_') /// disallow +_1.00
+               return -1;
+
+       { /// decimal point, if used, must be surrounded by at least one digit on each side
+               char *dot = strchr(s, '.');
+               if (dot) {
+                       if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1]))
+                               return -1;
+               }
+       }
+
+       /// zero must be followed by . or 'e', or NUL
+       if (s[0] == '0' && s[1] && !strchr("eE.", s[1]))
+               return -1;
+
+       /// just strip underscores and pass to strtod
+       while (*s && p < q) {
+               int ch = *s++;
+               if (ch == '_') {
+                       have_us = true;
+                       if (s[0] == '_') /// disallow '__'
+                               return -1;
+                       if (s[0] == 'e') /// disallow _e
+                               return -1;
+                       if (s[0] == 0)   /// disallow last char '_'
+                               return -1;
+                       continue; /// skip _
+               }
+               if (ch == 'I' || ch == 'N' || ch == 'F' || ch == 'A') /// inf and nan are case-sensitive.
+                       return -1;
+               if (ch == 'e' && s[0] == '_') /// disallow e_
+                       return -1;
+               *p++ = ch;
+       }
+       if (*s || p == q)
+               return -1; /// reached end of string or buffer is full?
+
+       *p = 0; /// cap with NUL
+
+       /// Run strtod on buf to get the value
+       char *endp;
+       errno = 0;
+       *ret = strtod(buf, &endp);
+       if (errno || *endp)
+               return -1;
+       if (have_us && (isnan(*ret) || isinf(*ret)))
+               return -1;
+       return 0;
+}
+
+int toml_value_string(toml_unparsed_t src, char **ret, int *len) {
+       bool multiline = false;
+       const char *sp;
+       const char *sq;
+
+       *ret = 0;
+       if (!src)
+               return -1;
+
+       /// First char must be a s-quote or d-quote
+       int qchar = src[0];
+       int srclen = strlen(src);
+       if (!(qchar == '\'' || qchar == '"')) {
+               return -1;
+       }
+
+       /// triple quotes?
+       if (qchar == src[1] && qchar == src[2]) {
+               multiline = true;      /// triple-quote implies multiline
+               sp = src + 3;          /// first char after quote
+               sq = src + srclen - 3; /// first char of ending quote
+
+               if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar))
+                       return -1; /// last 3 chars in src must be qchar
+
+               if (sp[0] == '\n') /// skip new line immediate after qchar
+                       sp++;
+               else if (sp[0] == '\r' && sp[1] == '\n')
+                       sp += 2;
+       } else {
+               sp = src + 1;          /// first char after quote
+               sq = src + srclen - 1; /// ending quote
+               if (!(sp <= sq && *sq == qchar)) /// last char in src must be qchar
+                       return -1;
+       }
+
+       /// at this point:
+       ///     sp points to first valid char after quote.
+       ///     sq points to one char beyond last valid char.
+       ///     string len is (sq - sp).
+       if (qchar == '\'')
+               *ret = norm_lit_str(sp, sq - sp, len, multiline, false, 0, 0);
+       else
+               *ret = norm_basic_str(sp, sq - sp, len, multiline, false, 0, 0);
+       return *ret ? 0 : -1;
+}
+
+toml_value_t toml_array_string(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_string(toml_array_unparsed(arr, idx), &ret.u.s, &ret.u.sl) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_bool(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_bool(toml_array_unparsed(arr, idx), &ret.u.b) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_int(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_int(toml_array_unparsed(arr, idx), &ret.u.i) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_double(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_double(toml_array_unparsed(arr, idx), &ret.u.d) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_timestamp(const toml_array_t *arr, int idx) {
+       toml_timestamp_t ts;
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_timestamp(toml_array_unparsed(arr, idx), &ts) == 0);
+       if (ret.ok) {
+               ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+               if (ret.ok)
+                       *ret.u.ts = ts;
+       }
+       return ret;
+}
+
+toml_value_t toml_table_string(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       toml_unparsed_t raw = toml_table_unparsed(tbl, key);
+       if (raw)
+               ret.ok = (toml_value_string(raw, &ret.u.s, &ret.u.sl) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_bool(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_bool(toml_table_unparsed(tbl, key), &ret.u.b) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_int(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_int(toml_table_unparsed(tbl, key), &ret.u.i) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_double(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_double(toml_table_unparsed(tbl, key), &ret.u.d) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_timestamp(const toml_table_t *tbl, const char *key) {
+       toml_timestamp_t ts;
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_timestamp(toml_table_unparsed(tbl, key), &ts) == 0);
+       if (ret.ok) {
+               ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+               if (ret.ok)
+                       *ret.u.ts = ts;
+       }
+       return ret;
+}
+
+static int parse_millisec(const char *p, const char **endp) {
+       int ret = 0;
+       int unit = 100; /// unit in millisec
+       for (; '0' <= *p && *p <= '9'; p++, unit /= 10)
+               ret += (*p - '0') * unit;
+       *endp = p;
+       return ret;
+}
+#endif // TOML_H
diff --git a/home-config/polybar/scripts/windowlist/windowlist.c b/home-config/polybar/scripts/windowlist/windowlist.c
new file mode 100644 (file)
index 0000000..da60813
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+    In this file is a stripped down version of wmctrl's source code
+
+    The original can be found at:
+        https://github.com/Conservatory/wmctrl
+
+    And a particularly helpful fork with some fixes and additions:
+        https://github.com/kfogel/wmctrl
+
+    Licensed under GPLv2
+
+    The function `list_windows` has been rewritten as `generate_window_list`
+    to get the properties in such a way that the list can be sorted based
+    on any criteria, and to be easy to format for the Polybar module.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xatom.h>
+#include "windowlist.h"
+
+#define MAX_PROPERTY_VALUE_LEN 4096
+
+char* get_property(Display* d, Window w, Atom xa_prop_type, char* prop_name,
+                   unsigned long* size) {
+    unsigned long ret_nitems, ret_bytes_after, tmp_size;
+    unsigned char* ret_prop;
+    int ret_format;
+    char* ret;
+
+    Atom xa_prop_name = XInternAtom(d, prop_name, False);
+    Atom xa_ret_type;
+
+    /*
+        MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
+
+        long_length = Specifies the length in 32-bit multiples of the
+                      data to be retrieved.
+
+        NOTE:  see
+        http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
+        In particular:
+
+        When the X window system was ported to 64-bit architectures, a
+        rather peculiar design decision was made. 32-bit quantities such
+        as Window IDs, atoms, etc, were kept as longs in the client side
+        APIs, even when long was changed to 64 bits.
+    */
+    if (XGetWindowProperty(d, w, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
+                           xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems,
+                           &ret_bytes_after, &ret_prop) != Success) {
+        return NULL;
+    }
+
+    if (xa_ret_type != xa_prop_type) {
+        XFree(ret_prop);
+        return NULL;
+    }
+
+    // null terminate the result to make string handling easier
+
+    tmp_size = (ret_format / 8) * ret_nitems;
+    // Correct 64 Architecture implementation of 32 bit data
+    if(ret_format==32) tmp_size *= sizeof(long)/4;
+    ret = malloc(tmp_size + 1);
+    memcpy(ret, ret_prop, tmp_size);
+    ret[tmp_size] = '\0';
+
+    if (size) {
+        *size = tmp_size;
+    }
+
+    XFree(ret_prop);
+    return ret;
+}
+
+Window* get_client_list(Display* d, unsigned long* size) {
+    Window* client_list = NULL;
+    char* msg = NULL;
+
+    msg = "_NET_CLIENT_LIST or _WIN_CLIENT_LIST";
+    client_list = (Window*) get_property(d, DefaultRootWindow(d), XA_WINDOW,
+                                         "_NET_CLIENT_LIST", size);
+    if (!client_list)
+        client_list = (Window*) get_property(d, DefaultRootWindow(d), XA_CARDINAL,
+                                             "_WIN_CLIENT_LIST", size);
+    if (!client_list)
+        fprintf(stderr, "Cannot get client list properties.\n(%s)\n", msg);
+
+    return client_list;
+}
+
+Window get_active_window(Display* d) {
+    char* prop;
+    unsigned long size;
+    Window ret = (Window) 0;
+
+    prop = get_property(d, DefaultRootWindow(d), XA_WINDOW,
+                        "_NET_ACTIVE_WINDOW", &size);
+    if (prop) {
+        ret = *((Window*) prop);
+        free(prop);
+    }
+
+    return(ret);
+}
+
+long get_desktop_id(Display* d, Window w, char* prop_name) {
+    /*
+        Get current desktop ID:
+            w: DefaultRootWindow(d)
+            prop_name: _NET_CURRENT_DESKTOP
+
+        Get desktop ID for given window:
+            w: WID
+            prop_name: _NET_WM_DESKTOP
+    */
+    unsigned long* desktop = NULL;
+    long desktop_id;
+    if (! (desktop = (unsigned long*) get_property(d, w, XA_CARDINAL,
+                                                     prop_name, NULL))) {
+        if (! (desktop = (unsigned long*) get_property(d, w, XA_CARDINAL,
+                                                         "_WIN_WORKSPACE", NULL))) {
+            fprintf(stderr, "Getting desktop property failed (%s or _WIN_WORKSPACE)\n", prop_name);
+            free(desktop);
+            return -2; // Desktop -2 doesn't exist
+        }
+    }
+    desktop_id = (long) *desktop;
+    free(desktop);
+    return desktop_id;
+}
+
+void calculate_window_middle_x_y(Display* d, Window w, int* x, int* y) {
+    Window junkroot;
+    int junkx, junky;
+    unsigned int width, height, border_width, depth;
+
+    XGetGeometry(d, w, &junkroot, &junkx, &junky, &width, &height, &border_width, &depth);
+    XTranslateCoordinates(d, w, junkroot, junkx, junky, x, y, &junkroot);
+
+    *x = *x + width/2;
+    *y = *y + height/2;
+}
+
+char* get_window_class(Display* d, Window w) {
+    char* empty_wname = "N/A";
+    char* empty = malloc(strlen(empty_wname) + 1);
+    strcpy(empty, empty_wname);
+
+    unsigned long size;
+    char* wm_class = get_property(d, w, XA_STRING, "WM_CLASS", &size);
+
+    if (!wm_class) {
+        return empty;
+    }
+
+    char* class = calloc(size, sizeof(char));
+
+    /*
+       WM_CLASS contains two consecutive null-terminated strings:
+       <Instance>\0<Class>\0
+       We want the second one, so point after the first null-terminator.
+
+       More explanation on this pretty unintuitive window property:
+       https://unix.stackexchange.com/questions/494169/wm-class-vs-wm-instance
+    */
+    char* pointer_to_class = strchr(wm_class, '\0') + 1;
+    strcpy(class, pointer_to_class);
+
+    if (strlen(class) == 0) {
+        free(wm_class);
+        free(class);
+        return empty;
+    }
+
+    free(empty);
+    free(wm_class);
+    return class;
+}
+
+char* get_window_title(Display *d, Window w) {
+    char* empty_wname = "N/A";
+    char* empty = malloc(strlen(empty_wname) + 1);
+    strcpy(empty, empty_wname);
+
+    char* title_utf8;
+
+    char* wm_name = get_property(d, w, XA_STRING, "WM_NAME", NULL);
+    char* net_wm_name = get_property(d, w,
+            XInternAtom(d, "UTF8_STRING", False), "_NET_WM_NAME", NULL);
+
+    if (net_wm_name) {
+        title_utf8 = calloc(strlen(net_wm_name) + 1, sizeof(char));
+        strcpy(title_utf8, net_wm_name);
+    }
+    else {
+        if (wm_name) {
+            title_utf8 = calloc(strlen(wm_name) + 1, sizeof(char));
+            strcpy(title_utf8, wm_name);
+        }
+        else {
+            free(wm_name);
+            free(net_wm_name);
+            return empty;
+        }
+    }
+
+    free(empty);
+    free(wm_name);
+    free(net_wm_name);
+
+    return title_utf8;
+}
+
+int error_catcher(Display* d, XErrorEvent* e) {
+    /*
+       Ignore BadWindow error instead of halting program
+
+       Because XLib is async, a window may already be destroyed when I try to do
+       something with its ID.
+
+       See these:
+
+       https://stackoverflow.com/questions/44025639/how-can-i-check-in-xlib-if-window-exists
+       https://stackoverflow.com/questions/51908828/xlib-and-badwindow
+    */
+
+    if (e->error_code == BadWindow || e->error_code == BadDrawable) {
+        // get_desktop_id() causes BadDrawable by extension of ignoring BadWindow:
+        // gets an undefined window as argument
+        fprintf(stderr, "Expected XError type %d\n", e->error_code);
+        return EXIT_SUCCESS;
+    }
+    fprintf(stderr, "Unexpected XError type: %d\n", e->error_code);
+    return EXIT_FAILURE;
+}
+
+struct window_props* generate_window_list(Display* d, long current_desktop_id, int* window_list_size) {
+    Window* client_list;
+    unsigned long client_list_size;
+
+    if (! (client_list = get_client_list(d, &client_list_size))) {
+        return NULL;
+    }
+
+    int n_clients = client_list_size / sizeof(Window);
+    struct window_props* window_list = malloc(n_clients * sizeof(struct window_props));
+
+    // Number of windows on current desktop
+    int w_count = 0;
+
+    XSetErrorHandler(error_catcher);
+
+    // Populate the list
+    for (int i = 0; i < n_clients; i++) {
+        Window w = client_list[i];
+
+        long desktop_id = get_desktop_id(d, w, "_NET_WM_DESKTOP");
+        if (desktop_id != current_desktop_id) {
+            continue;
+        }
+
+        struct window_props wp;
+        wp.id = w;
+        wp.class = get_window_class(d, w);
+        wp.title = get_window_title(d, w);
+        calculate_window_middle_x_y(d, w, &wp.x, &wp.y);
+
+        window_list[w_count] = wp;
+        w_count++;
+    }
+
+    XSetErrorHandler(NULL);
+    free(client_list);
+
+    *window_list_size = w_count;
+
+    // Remove uninitialized part from array end
+    window_list = realloc(window_list, *window_list_size * sizeof(struct window_props));
+
+    return window_list;
+}
diff --git a/home-config/polybar/scripts/windowlist/windowlist.h b/home-config/polybar/scripts/windowlist/windowlist.h
new file mode 100644 (file)
index 0000000..402b3b7
--- /dev/null
@@ -0,0 +1,12 @@
+#include <X11/Xlib.h>
+
+struct window_props {
+    Window id;
+    char* class;
+    char* title;
+    int x, y;
+};
+
+struct window_props* generate_window_list(Display* d, long current_desktop_id, int* n_wprops);
+Window get_active_window(Display* d);
+long get_desktop_id(Display* d, Window w, char* prop_name);
diff --git a/home-config/polybar/scripts/windowlist/windowlist.o b/home-config/polybar/scripts/windowlist/windowlist.o
new file mode 100644 (file)
index 0000000..12aca5b
Binary files /dev/null and b/home-config/polybar/scripts/windowlist/windowlist.o differ
diff --git a/home-config/rofi/darknix/appmenu.rasi b/home-config/rofi/darknix/appmenu.rasi
new file mode 100644 (file)
index 0000000..db25c4c
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * DARKNIX appsmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+/** Default settings, every widget inherits from this. */
+* {
+    /** Default background color is transparent. */
+    background-color: @transparent;
+    text-color:       @accent;
+}
+
+/**
+ * Entry box on top. 
+ */
+entry {
+    /** Background is grey */
+    background-color: @bg;
+    /** 4px padding on the inside of the border. */
+    padding:       4px;
+    /** when no text is set, show 'Type to filter' */
+    placeholder: "vidimu chi trovi: ";
+    placeholder-color: @fg;
+    /** inherit font setting from parent */
+    font: inherit;
+    cursor: text;
+       border: 2px 2px 2px 0px;
+    /** with a radius on the left two corners. */ 
+    border-radius: 0px 4px 4px 0px;
+    /** add matching border. */
+    border-color: @accent;
+}
+
+/**
+ * Input bar
+ */
+inputbar {
+    /** no spacing between widgets */
+    spacing: 0;
+    /** include entry and mode-switcher (removes prompt) */
+    children: [  icon-keyboard, entry ];
+    /** use monospace font.
+    font:   "Inconsolata Nerd Font 18";  */
+    margin: 1em 10em 2em;
+}
+
+entry {
+       vertical-align: 0.5;
+}
+
+/**
+ * Small icon in inputbar
+ */
+icon-keyboard {
+    /** give it a 2 pixel border, except on the right side. */
+    border:        2px 0px 2px 2px;
+    /** with a radius on the left two corners. */ 
+    border-radius: 4px 0px 0px 4px;
+    /** add matching border. */
+    border-color: @accent;
+    /** match background. */
+    background-color: @bg;
+    /** move icon away from right border. */
+    padding: 0px 10px 0px 10px;
+    /** Only use required space. */
+    expand: false;
+    /** icon is around 1.2 font width */
+    size: 1.2em;
+    /** Icon name, we use symbolic name here */
+    filename: "keyboard";
+}
+
+/**
+ * Main window widget
+ */
+window {
+    /** Place on top center of rofi window on the top center of the screen. */
+    anchor: north;
+    location: north;
+
+    /** 100% screen width */
+    width: 100%;
+    height: 100%;
+
+    /** Black transparent color. */
+    background-color: @bg-trans;
+    /** Small one 1 font width border on inside of window. */
+    padding: 0 0 2em;
+
+    /** border */
+    border:  0;
+}
+
+/**
+ * Main container in the window.
+ */
+mainbox { 
+    /** spacing between widgets */
+    spacing: 1em;
+}
+
+/**
+ * listview that shows entries.
+ */
+listview {
+    /** 4 rows. */
+    lines: 4;
+    /** 6 columns */
+    columns: 6;
+    /** add 1 em spacing between items */
+    spacing: 0.5em;
+    /** Don't reduce columns if less items are available. */
+    fixed-columns: true;
+}
+scrollbar {
+       enabled: false;
+}
+
+/**
+ * entry in listview.
+ */
+element {
+    /** clients are packed vertically. */
+    orientation:      vertical;
+    /** 2 px border */
+    border:           1px;
+    /** with 4px radius on corners. */
+    border-radius:    1em;
+    border-color:     @accent;
+    background-color: @bg-trans;
+    /** 4 px padding on the inside of border */
+    cursor: pointer;
+    padding:          6px;
+}
+
+/** selected element */
+element selected {
+    /** highlighted colors */
+    background-color: @bg-focus;
+    text-color:       @fg-list;
+}
+
+/** Entry icon */
+element-icon {
+    /** change size to 128 pixels. */
+    size: 128px;
+    cursor: inherit;
+}
+
+/** Entry text */
+element-text {
+    /* align font in (horizontally) center */
+    horizontal-align: 0.5;
+    cursor: inherit;
+}
+element-text selected {
+    border-radius: 1em;
+    background-color: @accent;
+    text-color: @bg;
+}
diff --git a/home-config/rofi/darknix/appslist.rasi b/home-config/rofi/darknix/appslist.rasi
new file mode 100644 (file)
index 0000000..d978038
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * DARKNIX appslist rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+scrollbar {
+    disabled: true;
+}
+window {
+    fullscreen: true;
+    background-color: @bg-trans;
+    width: 100%;
+    position: north;
+}
+mainbox {
+    children: [ listview ];
+    border: 0;
+    padding: 0;
+}
+inputbar {
+}
+prompt {
+    enabled: false;
+}
+entry {
+    text-color: @accent;
+    background-color: @bg-focus;
+    border: 1px solid;
+    border-color: @accent;
+    border-radius: 5px;
+    padding: 10px; 
+    horizontal-align: 0.0;
+    vertical-align: 0.5;
+    placeholder: "e mo chi voi?";
+    placeholder-color: @fg;
+}
+listview {
+    lines: 3;
+    columns: 2;
+    expand: true;
+    cycle: true;
+    /** Don't reduce columns if less items are available. */
+    fixed-columns: true;
+    spacing: 1em;
+    /*  60px is the height of the items  */
+    padding: 1em 3em;
+/*    orientation: vertical;*/
+}
+/**
+ * entry in listview.
+ */
+element {
+    /** clients are packed vertically. */
+    orientation:      vertical;
+    /** 2 px border */
+    border:           1px;
+    /** with 4px radius on corners. */
+    border-radius:    1em;
+    border-color:     @accent;
+    background-color: @bg-trans;
+    /** 4 px padding on the inside of border */
+    cursor: pointer;
+    padding:          6px;
+}
+
+/** selected element */
+element selected {
+    /** highlighted colors */
+    background-color: @bg-focus;
+    text-color:       @fg-list;
+}
+
+/** Entry icon */
+element-icon {
+    /** change size to 128 pixels. */
+    size: 128px;
+    cursor: inherit;
+}
+
+/** Entry text */
+element-text {
+    /* align font in (horizontally) center */
+    vertical-align: 0.5;
+    cursor: inherit;
+}
+element-text selected {
+    border-radius: 1em;
+    background-color: @accent;
+    text-color: @bg;
+}
diff --git a/home-config/rofi/darknix/i3exit.rasi b/home-config/rofi/darknix/i3exit.rasi
new file mode 100644 (file)
index 0000000..c142a60
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * DARKNIX scrotmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+scrollbar {
+    disabled: true;
+}
+window {
+    fullscreen: true;
+    background-color: @bg-trans;
+    width: 100%;
+    position: north;
+}
+mainbox {
+    children: [ textbox1, listview ];
+    border: 0;
+    padding: 0;
+}
+textbox1 {
+    margin: 1em;
+    padding: 0;
+    width: 100%;
+    text-color: @accent;
+    font: "Montserrat 24";
+    str: "Are you sure you want to exit?";
+    vertical-align: 0.2;
+    horizontal-align: 0.5;
+}
+listview {
+    columns: 2;
+    lines: 1;
+    expand: false;
+    cycle: true;
+    spacing: 4em;
+    /*  60px is the height of the items  */
+    padding: calc(50% - 5em) 22em;
+    orientation: horizontal;
+}
+element {
+    border: 1px;
+    border-radius: 1em 2em;
+    border-color: @accent;
+    background-color: @bg;
+    orientation: horizontal;
+    padding: 2em 0;
+}
+element selected {
+    background-color: @accent;
+/*    border-color: @bg-focus;*/
+}
+element-text {
+    font: "Inconsolata Nerd Font 70";
+    horizontal-align: 0.35;
+    vertical-align: 0.5;
+}
+element-text selected {
+    text-color: @bg;
+}
\ No newline at end of file
diff --git a/home-config/rofi/darknix/libs/reset.rasi b/home-config/rofi/darknix/libs/reset.rasi
new file mode 100644 (file)
index 0000000..fdd8764
--- /dev/null
@@ -0,0 +1,11 @@
+@import "/home/danix/.cache/wal/darknix-colors.rasi"
+
+* {
+       /* STYLE RESET */
+       border: 0;
+       padding: 0;
+       margin: 0;
+       background-color: @transparent;
+       text-color: @accent;
+}
+
diff --git a/home-config/rofi/darknix/libs/settings.rasi b/home-config/rofi/darknix/libs/settings.rasi
new file mode 100644 (file)
index 0000000..d237c2f
--- /dev/null
@@ -0,0 +1,19 @@
+configuration {
+       modi: "run,window,drun,ssh";
+/*     font: "Inconsolata Nerd Font Regular 12";*/
+       drun-display-format: "{icon} <span weight='light' size='small'><i>{name}</i></span>";
+    display-drun: "Apps";
+       show-icons: true;
+       icon-theme: "MB-Blueberry-Suru-GLOW";
+       drun-match-fields: "name,generic,exec,category";
+       hide-scrollbar: true;
+       cycle: true;
+/*    fullscreen: true;*/
+       sidebar-mode: false;
+       terminal: "kitty";
+       ssh-client: "ssh";
+       ssh-command: "{terminal} -e {ssh-client} {host}";
+       run-command: "{cmd}";
+       run-shell-command: "{terminal} -e {cmd}";
+       cache-dir: "/home/danix/.cache/";
+}
diff --git a/home-config/rofi/darknix/main.rasi b/home-config/rofi/darknix/main.rasi
new file mode 100644 (file)
index 0000000..6b8e32e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * BLACKPEARL appsmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+/** Default settings, every widget inherits from this. */
+* {
+    /** Default background color is transparent. */
+    background-color: transparent;
+    /** Default text is white */
+    text-color:       @accent;
+}
+
+window {
+    anchor: center;
+    location: center;
+    width: 600px;
+    height: 400px;
+    margin: 0;
+    background-color: @bg-trans;
+}
+mainbox {
+    background-color: @bg-trans;
+    children: [ inputbar, listview ];
+    border:  0;
+    padding: 0;
+}
+inputbar {
+    enabled: false;
+}
+prompt {
+    enabled: false;
+}
+listview {
+    margin: 20px;
+    columns: 1;
+    lines: 12;
+    expand: false;
+    cycle: true;
+    spacing: 10px;
+    /*padding: 50px 10px;*/
+}
+element {
+    text-color: @fg;
+}
+element.selected {
+    background-color: @bg-focus;
+    text-color: @accent;
+}
diff --git a/home-config/rofi/darknix/notes.rasi b/home-config/rofi/darknix/notes.rasi
new file mode 100644 (file)
index 0000000..d15ab18
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * DARKNIX notes rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+window {
+       anchor: center;
+       location: center;
+       width: 600px;
+       height: 400px;
+       margin: 0;
+       background-color: @bg-trans;
+}
+mainbox {
+       background-color: @bg-trans;
+       children: [ inputbar, listview ];
+    border:  0;
+    padding: 0;
+}
+inputbar {
+}
+prompt {
+/*     enabled: false;*/
+       text-color: @accent;
+       vertical-align: 0.5;
+       padding: 0 10px;
+}
+entry {
+       text-color: @accent;
+       background-color: @bg-focus;
+       border: 1px solid;
+       border-color: @accent;
+       border-radius: 5px;
+       padding: 10px; 
+       horizontal-align: 0.0;
+       vertical-align: 0.5;
+       placeholder: "search notes...";
+       placeholder-color: @fg;
+}
+listview {
+       margin: 60px 0;
+       columns: 1;
+       lines: 12;
+       expand: false;
+       /*cycle: true;*/
+       spacing: 10px;
+       /*padding: 50px 10px;*/
+}
+element {
+       text-color: @fg;
+}
+element.selected {
+       background-color: @bg-focus;
+       text-color: @accent;
+}
diff --git a/home-config/rofi/darknix/powermenu.rasi b/home-config/rofi/darknix/powermenu.rasi
new file mode 100644 (file)
index 0000000..baa5d0e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * DARKNIX powermenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+scrollbar {
+    disabled: true;
+}
+window {
+    fullscreen: true;
+    background-color: @bg-trans;
+    width: 100%;
+    position: north;
+}
+mainbox {
+    children: [ listview ];
+    border: 0;
+    padding: 0;
+}
+listview {
+    columns: 4;
+    lines: 1;
+    expand: false;
+    cycle: true;
+    spacing: 3em;
+    /*  60px is the height of the items  */
+    padding: calc(50% - 5em) 6em;
+    orientation: horizontal;
+}
+element {
+    border: 1px;
+    border-radius: 1em 2em;
+    border-color: @accent;
+    background-color: @bg;
+    orientation: horizontal;
+    padding: 2em 0;
+}
+element selected {
+    background-color: @accent;
+/*    border-color: @bg-focus;*/
+}
+element-text {
+    font: "Inconsolata Nerd Font 70";
+    horizontal-align: 0.35;
+    vertical-align: 0.5;
+}
+element-text selected {
+    text-color: @bg;
+}
diff --git a/home-config/rofi/darknix/runner.rasi b/home-config/rofi/darknix/runner.rasi
new file mode 100644 (file)
index 0000000..9510872
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * DARKNIX runner rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+window {
+       anchor: north;
+       location: north;
+       width: 600px;
+       height: 400px;
+       margin: 0;
+       y-offset: 28px;
+       background-color: @bg-trans;
+}
+mainbox {
+       background-color: @bg-trans;
+       children: [ inputbar, listview ];
+    border:  0;
+    padding: 0;
+}
+inputbar {
+}
+prompt {
+       enabled: false;
+}
+entry {
+       text-color: @accent;
+       background-color: @bg-focus;
+       border: 1px solid;
+       border-color: @accent;
+       border-radius: 5px;
+       padding: 10px; 
+       horizontal-align: 0.0;
+       vertical-align: 0.5;
+       placeholder: "e mo chi voi?";
+       placeholder-color: @fg;
+}
+listview {
+       margin: 60px 0;
+       columns: 1;
+       lines: 12;
+       expand: false;
+       /*cycle: true;*/
+       spacing: 10px;
+       /*padding: 50px 10px;*/
+}
+element {
+       text-color: @fg;
+}
+element.selected {
+       background-color: @bg-focus;
+       text-color: @accent;
+}
diff --git a/home-config/rofi/darknix/scrotmenu.rasi b/home-config/rofi/darknix/scrotmenu.rasi
new file mode 100644 (file)
index 0000000..6440709
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * DARKNIX scrotmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+scrollbar {
+    disabled: true;
+}
+window {
+    fullscreen: true;
+    background-color: @bg-trans;
+    width: 100%;
+    position: north;
+}
+mainbox {
+    children: [ listview ];
+    border: 0;
+    padding: 0;
+}
+listview {
+    columns: 2;
+    lines: 1;
+    expand: false;
+    cycle: true;
+    spacing: 4em;
+    /*  60px is the height of the items  */
+    padding: calc(50% - 5em) 22em;
+    orientation: horizontal;
+}
+element {
+    border: 1px;
+    border-radius: 1em 2em;
+    border-color: @accent;
+    background-color: @bg;
+    orientation: horizontal;
+    padding: 2em 0;
+}
+element selected {
+    background-color: @accent;
+/*    border-color: @bg-focus;*/
+}
+element-text {
+    font: "Inconsolata Nerd Font 70";
+    horizontal-align: 0.35;
+    vertical-align: 0.5;
+}
+element-text selected {
+    text-color: @bg;
+}
\ No newline at end of file
diff --git a/home-config/rofi/darknix/sshmenu.rasi b/home-config/rofi/darknix/sshmenu.rasi
new file mode 100644 (file)
index 0000000..2056c4d
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * DARKNIX appsmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+/** Default settings, every widget inherits from this. */
+* {
+    /** Default background color is transparent. */
+    background-color: @bg-trans;
+    text-color:       @accent;
+}
+
+/**
+ * Entry box on top. 
+ */
+entry {
+    /** Background is grey */
+    background-color: @bg;
+    /** 4px padding on the inside of the border. */
+    padding:       4px;
+    /** when no text is set, show 'Type to filter' */
+    placeholder: "cerca connessione SSH:";
+    placeholder-color: @fg;
+    vertical-align: 0.5;
+    /** inherit font setting from parent */
+    font: inherit;
+    cursor: text;
+       border: 2px 2px 2px 0px;
+    /** with a radius on the left two corners. */ 
+    border-radius: 0px 4px 4px 0px;
+    /** add matching border. */
+    border-color: @accent;
+}
+
+/**
+ * Input bar
+ */
+inputbar {
+    /** no spacing between widgets */
+    spacing: 0;
+    /** include entry and mode-switcher (removes prompt) */
+    children: [  icon-keyboard, entry ];
+    /** use monospace font. */
+    font:   "Montserrat 18";
+    margin: 0 10em 1em;
+}
+
+/**
+ * Small icon in inputbar
+ */
+icon-keyboard {
+    /** give it a 2 pixel border, except on the right side. */
+    border:        2px 0px 2px 2px;
+    /** with a radius on the left two corners. */ 
+    border-radius: 4px 0px 0px 4px;
+    /** add matching border. */
+    border-color: @accent;
+    /** match background. */
+    background-color: @bg;
+    /** move icon away from right border. */
+    padding: 0px 10px 0px 10px;
+    /** Only use required space. */
+    expand: false;
+    /** icon is around 1.2 font width */
+    size: 1.2em;
+    /** Icon name, we use symbolic name here */
+    filename: "keyboard";
+}
+
+/**
+ * Main window widget
+ */
+window {
+    fullscreen: true;
+    /** Place on top center of rofi window on the top center of the screen. */
+    anchor: north;
+    location: north;
+
+    /** 100% screen width */
+    width: 100%;
+
+    /** Black transparent color. */
+    background-color: @bg-trans;
+    /** Small one 1 font width border on inside of window. */
+    padding: 0 0 2em;
+
+    /** border */
+    border:  0;
+}
+
+/**
+ * Main container in the window.
+ */
+mainbox { 
+    /** spacing between widgets */
+    spacing: 1em;
+}
+
+/**
+ * listview that shows entries.
+ */
+listview {
+    /** 4 rows. */
+    lines: 3;
+    /** 6 columns */
+    columns: 2;
+    /** add 1 em spacing between items */
+    spacing: 1em;
+    /** Don't reduce columns if less items are available. */
+    fixed-columns: true;
+}
+scrollbar {
+       enabled: false;
+}
+
+/**
+ * entry in listview.
+ */
+element {
+    /** clients are packed vertically. */
+    orientation: vertical;
+    /** 2 px border */
+    border: 1px;
+    /** with 4px radius on corners. */
+    border-radius:    1em;
+    border-color:     @accent;
+    background-color: @bg-trans;
+    /** 4 px padding on the inside of border */
+    cursor: pointer;
+    padding: 2em 3em 3em;
+}
+
+/** selected element */
+element selected {
+    /** highlighted colors */
+    background-color: @accent;
+}
+/** Entry text */
+element-text {
+    /* align font in (horizontally) center */
+    vertical-align: 0.5;
+    horizontal-align: 0.5;
+    cursor: inherit;
+    text-color: @accent;
+}
+element-text selected {
+    border-radius: 1em;
+    background-color: @accent;
+    text-color: @bg;
+}
diff --git a/home-config/rofi/darknix/utilsmenu.rasi b/home-config/rofi/darknix/utilsmenu.rasi
new file mode 100644 (file)
index 0000000..9c155d5
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * DARKNIX utilsmenu rofi theme
+ * tested on a 1366x768 screen
+ */
+
+/* IMPORTS */
+@import "libs/settings.rasi"
+@import "libs/reset.rasi"
+
+scrollbar {
+    disabled: true;
+}
+window {
+    fullscreen: true;
+    background-color: @bg-trans;
+    width: 100%;
+    position: north;
+}
+mainbox {
+    children: [ listview ];
+    border: 0;
+    padding: 0;
+}
+listview {
+    columns: 2;
+    lines: 1;
+    expand: false;
+    cycle: true;
+    spacing: 4em;
+    /*  60px is the height of the items  */
+    padding: calc(50% - 5em) 22em;
+    orientation: horizontal;
+}
+element {
+    border: 1px;
+    border-radius: 1em 2em;
+    border-color: @accent;
+    background-color: @bg;
+    orientation: horizontal;
+    padding: 2em 0;
+}
+element selected {
+    background-color: @accent;
+/*    border-color: @bg-focus;*/
+}
+element-text {
+    font: "Inconsolata Nerd Font 70";
+    horizontal-align: 0.35;
+    vertical-align: 0.5;
+}
+element-text selected {
+    text-color: @bg;
+}
\ No newline at end of file
diff --git a/home-config/rofi/rofi-symbols/1.math.symbols b/home-config/rofi/rofi-symbols/1.math.symbols
new file mode 100644 (file)
index 0000000..218df36
--- /dev/null
@@ -0,0 +1,39 @@
+# operations
+× - multiplication, cross product
+⋅ - interpunct, middle dot, multiplication, dot product,
+± - plus-minus
+± - minus-plus
+÷ - obelus, division sign
+
+⊕ - circled plus, oplus
+⊖︀ - circled minus
+⊗ - circled times, circled cross
+
+# equality
+≠ - inequality, not equal
+≈ - approximately equal
+≡ - equality, identity, congruence modulo
+∝ - proportional
+≥ - inequality, greater than or equal
+≤ - inequality, less than or equal
+
+# calculus
+∇ - del, nabla, gradient, divergence
+δ - delta, change, derivative
+∂ - partial derivative, cursive d
+∫ - integral, antiderivative
+∞ - lemniscate, infinity
+
+# sets
+ℵ - aleph, infinite set, cardinality
+∈ - element of set, belongs
+∉ - not an element of set, does not belong
+∋ - contains as member, includes
+∌ - does not contain as member, excludes
+
+Ø - O with stroke, null, fancy zero
+ø - o with stroke, null
+∅ - empty set, null
+
+# misc
+— - horizontal line
diff --git a/home-config/rofi/rofi-symbols/2.greek(distinct).symbols b/home-config/rofi/rofi-symbols/2.greek(distinct).symbols
new file mode 100644 (file)
index 0000000..b28992d
--- /dev/null
@@ -0,0 +1,49 @@
+#Α - ALPHA
+α - alpha
+#Β - BETA
+β - beta
+Γ - GAMMA
+γ - gamma
+Δ - DELTA, difference, discriminant
+δ - delta, change, derivative
+#Ε - EPSILON
+ε - epsilon, error
+#Ζ - ZETA
+ζ - zeta
+#Η - ETA
+η - eta
+Θ - THETA
+θ - theta
+#Ι - IOTA
+ι - iota
+#Κ - KAPPA
+#κ - kappa
+Λ - LAMBDA
+λ - lambda, wavelength, radioactivity, half-life
+#Μ - MU
+μ - mu, micro
+#Ν - NU
+ν - nu
+Ξ - XI
+ξ - xi
+#Ο - OMICRON
+#ο - omicron
+Π - PI, product in range of series
+π - pi, circumference
+#Ρ - RHO
+ρ - rho
+Σ - SIGMA, summation in range of series
+σ - sigma, standard deviation
+ς - sigma
+#Τ - TAU
+τ - tau
+#Υ - UPSILON
+υ - upsilon
+Φ - PHI
+φ - phi, golden ratio
+#Χ - CHI
+χ - chi
+Ψ - PSI
+ψ - psi
+Ω - OMEGA
+ω - omega
diff --git a/picom/picom.conf b/picom/picom.conf
new file mode 100644 (file)
index 0000000..75cc6b8
--- /dev/null
@@ -0,0 +1,431 @@
+#################################
+#             Shadows           #
+#################################
+
+
+# Enabled client-side shadows on windows. Note desktop windows
+# (windows with '_NET_WM_WINDOW_TYPE_DESKTOP') never get shadow,
+# unless explicitly requested using the wintypes option.
+#
+# shadow = false
+shadow = true;
+
+# The blur radius for shadows, in pixels. (defaults to 12)
+# shadow-radius = 12
+shadow-radius = 7;
+
+# The opacity of shadows. (0.0 - 1.0, defaults to 0.75)
+# shadow-opacity = .75
+
+# The left offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-x = -15
+shadow-offset-x = -7;
+
+# The top offset for shadows, in pixels. (defaults to -15)
+# shadow-offset-y = -15
+shadow-offset-y = -7;
+
+# Red color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-red = 0
+
+# Green color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-green = 0
+
+# Blue color value of shadow (0.0 - 1.0, defaults to 0).
+# shadow-blue = 0
+
+# Hex string color value of shadow (#000000 - #FFFFFF, defaults to #000000). This option will override options set shadow-(red/green/blue)
+# shadow-color = "#000000"
+
+# Specify a list of conditions of windows that should have no shadow.
+# shadow-exclude = []
+shadow-exclude = [
+  "name = 'Notification'",
+  "class_g = 'Conky'",
+  "class_g ?= 'Notify-osd'",
+  "class_g = 'Cairo-clock'",
+  "class_g = 'steam'",
+  "class_g = 'org.nickvision.cavalier'"
+];
+
+# Specify a list of conditions of windows that should have no shadow painted over, such as a dock window.
+clip-shadow-above = [
+  "class_g = 'Conky'",
+  "class_g = 'org.nickvision.cavalier'"
+] 
+
+# Crop shadow of a window fully on a particular monitor to that monitor. This is
+# currently implemented using the X RandR extension.
+# crop-shadow-to-monitor = false
+
+
+#################################
+#           Fading              #
+#################################
+
+
+# Fade windows in/out when opening/closing and when opacity changes,
+#  unless no-fading-openclose is used.
+# fading = false
+fading = true;
+
+# Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028)
+# fade-in-step = 0.028
+fade-in-step = 0.0066;
+
+# Opacity change between steps while fading out. (0.01 - 1.0, defaults to 0.03)
+# fade-out-step = 0.03
+fade-out-step = 0.01;
+
+# The time between steps in fade step, in milliseconds. (> 0, defaults to 10)
+fade-delta = 1
+
+# Specify a list of conditions of windows that should not be faded.
+fade-exclude = [
+  "class_g = 'steam'"
+]
+
+# Do not fade on window open/close.
+no-fading-openclose = false
+
+# Do not fade destroyed ARGB windows with WM frame. Workaround of bugs in Openbox, Fluxbox, etc.
+# no-fading-destroyed-argb = false
+
+
+#################################
+#   Transparency / Opacity      #
+#################################
+
+
+# Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
+# inactive-opacity = 1
+inactive-opacity = 0.8;
+
+# Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default)
+# frame-opacity = 1.0
+frame-opacity = 0.7;
+
+# Let inactive opacity set by -i override the '_NET_WM_WINDOW_OPACITY' values of windows.
+# inactive-opacity-override = true
+inactive-opacity-override = false;
+
+# Default opacity for active windows. (0.0 - 1.0, defaults to 1.0)
+# active-opacity = 1.0
+
+# Dim inactive windows. (0.0 - 1.0, defaults to 0.0)
+# inactive-dim = 0.0
+
+# Specify a list of conditions of windows that should never be considered focused.
+# focus-exclude = []
+#focus-exclude = [ "class_g = 'Cairo-clock'" ];
+
+# Use fixed inactive dim value, instead of adjusting according to window opacity.
+inactive-dim-fixed = 1.0
+
+# Specify a list of opacity rules, in the format `PERCENT:PATTERN`,
+# like `50:name *= "Firefox"`. picom-trans is recommended over this.
+# Note we don't make any guarantee about possible conflicts with other
+# programs that set '_NET_WM_WINDOW_OPACITY' on frame or client windows.
+# example:
+#    opacity-rule = [ "80:class_g = 'URxvt'" ];
+#
+opacity-rule = [
+  "80:class_g *= 'pcmanfm-qt'",
+  "70:class_g *= 'org.nickvision.cavalier'",
+  "90:class_g *= 'TelegramDesktop'",
+  "100:class_g *= 'steam'"
+]
+
+
+#################################
+#           Corners             #
+#################################
+
+# Sets the radius of rounded window corners. When > 0, the compositor will
+# round the corners of windows. Does not interact well with
+# `transparent-clipping`.
+corner-radius = 0
+
+# Exclude conditions for rounded corners.
+rounded-corners-exclude = [
+  "window_type = 'dock'",
+  "window_type = 'desktop'"
+];
+
+
+#################################
+#     Background-Blurring       #
+#################################
+
+
+# Parameters for background blurring, see the *BLUR* section for more information.
+blur-method = "kernel"
+blur-size = 10
+#
+blur-deviation = 5
+#
+# blur-strength = 5
+
+# Blur background of semi-transparent / ARGB windows.
+# Bad in performance, with driver-dependent behavior.
+# The name of the switch may change without prior notifications.
+#
+# blur-background = false
+
+# Blur background of windows when the window frame is not opaque.
+# Implies:
+#    blur-background
+# Bad in performance, with driver-dependent behavior. The name may change.
+#
+# blur-background-frame = false
+
+
+# Use fixed blur strength rather than adjusting according to window opacity.
+# blur-background-fixed = false
+
+
+# Specify the blur convolution kernel, with the following format:
+# example:
+#blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1";
+blur-kern = "15,15,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,";
+#
+#blur-kern = ""
+#blur-kern = "3x3box";
+
+
+# Exclude conditions for background blur.
+# blur-background-exclude = []
+#blur-background-exclude = [
+#  "window_type = 'dock'",
+#  "window_type = 'desktop'"
+#];
+
+#################################
+#       General Settings        #
+#################################
+
+# Enable remote control via D-Bus. See the man page for more details.
+# dbus = true
+
+# Daemonize process. Fork to background after initialization. Causes issues with certain (badly-written) drivers.
+daemon = true
+
+# Specify the backend to use: `xrender`, `glx`, `egl` or `xr_glx_hybrid`.
+# `xrender` is the default one.
+#
+backend = "glx"
+#backend = "xrender";
+
+# Use higher precision during rendering, and apply dither when presenting the
+# rendered screen. Reduces banding artifacts, but might cause performance
+# degradation. Only works with OpenGL.
+dithered-present = false;
+
+# Enable/disable VSync.
+# vsync = false
+vsync = true;
+
+# Try to detect WM windows (a non-override-redirect window with no
+# child that has 'WM_STATE') and mark them as active.
+#
+# mark-wmwin-focused = false
+mark-wmwin-focused = true;
+
+# Mark override-redirect windows that doesn't have a child window with 'WM_STATE' focused.
+# mark-ovredir-focused = false
+mark-ovredir-focused = true;
+
+# Try to detect windows with rounded corners and don't consider them
+# shaped windows. The accuracy is not very high, unfortunately.
+#
+# detect-rounded-corners = false
+detect-rounded-corners = true;
+
+# Detect '_NET_WM_WINDOW_OPACITY' on client windows, useful for window managers
+# not passing '_NET_WM_WINDOW_OPACITY' of client windows to frame windows.
+#
+# detect-client-opacity = false
+detect-client-opacity = true;
+
+# Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window,
+# rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy,
+# provided that the WM supports it.
+#
+# use-ewmh-active-win = false
+
+# Unredirect all windows if a full-screen opaque window is detected,
+# to maximize performance for full-screen windows. Known to cause flickering
+# when redirecting/unredirecting windows.
+#
+unredir-if-possible = true
+
+# Delay before unredirecting the window, in milliseconds. Defaults to 0.
+# unredir-if-possible-delay = 0
+
+# Conditions of windows that shouldn't be considered full-screen for unredirecting screen.
+# unredir-if-possible-exclude = []
+
+# Use 'WM_TRANSIENT_FOR' to group windows, and consider windows
+# in the same group focused at the same time.
+#
+# detect-transient = false
+detect-transient = true;
+
+# Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same
+# group focused at the same time. This usually means windows from the same application
+# will be considered focused or unfocused at the same time.
+# 'WM_TRANSIENT_FOR' has higher priority if detect-transient is enabled, too.
+#
+detect-client-leader = true
+
+# Resize damaged region by a specific number of pixels.
+# A positive value enlarges it while a negative one shrinks it.
+# If the value is positive, those additional pixels will not be actually painted
+# to screen, only used in blur calculation, and such. (Due to technical limitations,
+# with use-damage, those pixels will still be incorrectly painted to screen.)
+# Primarily used to fix the line corruption issues of blur,
+# in which case you should use the blur radius value here
+# (e.g. with a 3x3 kernel, you should use `--resize-damage 1`,
+# with a 5x5 one you use `--resize-damage 2`, and so on).
+# May or may not work with *--glx-no-stencil*. Shrinking doesn't function correctly.
+#
+# resize-damage = 1
+
+# Specify a list of conditions of windows that should be painted with inverted color.
+# Resource-hogging, and is not well tested.
+#
+# invert-color-include = []
+
+# GLX backend: Avoid using stencil buffer, useful if you don't have a stencil buffer.
+# Might cause incorrect opacity when rendering transparent content (but never
+# practically happened) and may not work with blur-background.
+# My tests show a 15% performance boost. Recommended.
+#
+# glx-no-stencil = false
+
+# GLX backend: Avoid rebinding pixmap on window damage.
+# Probably could improve performance on rapid window content changes,
+# but is known to break things on some drivers (LLVMpipe, xf86-video-intel, etc.).
+# Recommended if it works.
+#
+# glx-no-rebind-pixmap = false
+
+# Disable the use of damage information.
+# This cause the whole screen to be redrawn every time, instead of the part of the screen
+# has actually changed. Potentially degrades the performance, but might fix some artifacts.
+# The opposing option is use-damage
+#
+# no-use-damage = false
+use-damage = true;
+
+# Use X Sync fence to sync clients' draw calls, to make sure all draw
+# calls are finished before picom starts drawing. Needed on nvidia-drivers
+# with GLX backend for some users.
+#
+xrender-sync-fence = true
+
+# GLX backend: Use specified GLSL fragment shader for rendering window
+# contents. Read the man page for a detailed explanation of the interface.
+#
+# window-shader-fg = "default"
+
+# Use rules to set per-window shaders. Syntax is SHADER_PATH:PATTERN, similar
+# to opacity-rule. SHADER_PATH can be "default". This overrides window-shader-fg.
+#
+# window-shader-fg-rule = [
+#   "my_shader.frag:window_type != 'dock'"
+# ]
+
+# Force all windows to be painted with blending. Useful if you
+# have a glx-fshader-win that could turn opaque pixels transparent.
+#
+# force-win-blend = false
+
+# Do not use EWMH to detect fullscreen windows.
+# Reverts to checking if a window is fullscreen based only on its size and coordinates.
+#
+# no-ewmh-fullscreen = false
+
+# Dimming bright windows so their brightness doesn't exceed this set value.
+# Brightness of a window is estimated by averaging all pixels in the window,
+# so this could comes with a performance hit.
+# Setting this to 1.0 disables this behaviour. Requires --use-damage to be disabled. (default: 1.0)
+#
+# max-brightness = 1.0
+
+# Make transparent windows clip other windows like non-transparent windows do,
+# instead of blending on top of them.
+#
+# transparent-clipping = false
+
+# Specify a list of conditions of windows that should never have transparent
+# clipping applied. Useful for screenshot tools, where you need to be able to
+# see through transparent parts of the window.
+#
+# transparent-clipping-exclude = []
+
+# Set the log level. Possible values are:
+#  "trace", "debug", "info", "warn", "error"
+# in increasing level of importance. Case doesn't matter.
+# If using the "TRACE" log level, it's better to log into a file
+# using *--log-file*, since it can generate a huge stream of logs.
+#
+# log-level = "debug"
+log-level = "warn";
+
+# Set the log file.
+# If *--log-file* is never specified, logs will be written to stderr.
+# Otherwise, logs will to written to the given file, though some of the early
+# logs might still be written to the stderr.
+# When setting this option from the config file, it is recommended to use an absolute path.
+#
+# log-file = "/path/to/your/log/file"
+
+# Show all X errors (for debugging)
+# show-all-xerrors = false
+
+# Write process ID to a file.
+# write-pid-path = "/path/to/your/log/file"
+
+# Window type settings
+#
+# 'WINDOW_TYPE' is one of the 15 window types defined in EWMH standard:
+#     "unknown", "desktop", "dock", "toolbar", "menu", "utility",
+#     "splash", "dialog", "normal", "dropdown_menu", "popup_menu",
+#     "tooltip", "notification", "combo", and "dnd".
+#
+# Following per window-type options are available: ::
+#
+#   fade, shadow:::
+#     Controls window-type-specific shadow and fade settings.
+#
+#   opacity:::
+#     Controls default opacity of the window type.
+#
+#   focus:::
+#     Controls whether the window of this type is to be always considered focused.
+#     (By default, all window types except "normal" and "dialog" has this on.)
+#
+#   full-shadow:::
+#     Controls whether shadow is drawn under the parts of the window that you
+#     normally won't be able to see. Useful when the window has parts of it
+#     transparent, and you want shadows in those areas.
+#
+#   clip-shadow-above:::
+#     Controls whether shadows that would have been drawn above the window should
+#     be clipped. Useful for dock windows that should have no shadow painted on top.
+#
+#   redir-ignore:::
+#     Controls whether this type of windows should cause screen to become
+#     redirected again after been unredirected. If you have unredir-if-possible
+#     set, and doesn't want certain window to cause unnecessary screen redirection,
+#     you can set this to `true`.
+#
+wintypes:
+{
+  tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
+  dock = { shadow = false; clip-shadow-above = true; }
+  dnd = { shadow = false; }
+  popup_menu = { opacity = 0.8; }
+  dropdown_menu = { opacity = 0.8; }
+};
diff --git a/polybar/config b/polybar/config
new file mode 100644 (file)
index 0000000..e5706d8
--- /dev/null
@@ -0,0 +1,118 @@
+[colors]
+background = ${xrdb:color0:#bb000000}
+foreground = ${xrdb:color7:#222}
+foreground-alt = ${xrdb:color7:#cdcdcd}
+primary = ${xrdb:color1:#01ebea}
+secondary = ${xrdb:color2:#f57c00}
+alert = ${xrdb:color3:#f61717}
+
+[settings]
+pseudo-transparency = false
+
+
+[bar/top]
+override-redirect = true
+bottom = false
+width = 100%
+height = 28px
+underline-size = 1px
+underline-color = ${colors.primary}
+background = ${colors.background}
+foreground = ${colors.foreground}
+#separator = ·
+separator-padding = 1
+separator-foreground = ${colors.secondary}
+font-0 = "Inconsolata Nerd Font:weight=regular;2"
+padding = 1
+modules-left = slackware gmail-0 gmail-2 gmail-1 pulseaudio 
+modules-center = xworkspaces
+modules-right = info-airqualityindex weather xkeyboard
+dpi = 96
+enable-ipc = true
+
+[bar/bottom]
+override-redirect = true
+bottom = true
+width = 100%
+height = 24px
+background = ${colors.background}
+foreground = ${colors.foreground}
+#separator = ·
+#separator-padding = 1
+separator-foreground = ${colors.secondary}
+font-0 = "Inconsolata Nerd Font:weight=regular;2"
+padding = 1px
+modules-left = duckstation dunst-snooze 
+modules-center = windowlist
+modules-right = date
+dpi = 96
+enable-ipc = true
+
+# MODULES
+
+# airquality
+include-file = /home/danix/.config/polybar/modules/airquality.ini
+
+# duckstation
+include-file = /home/danix/.config/polybar/modules/duckstation.ini
+
+# i3
+#include-file = /home/danix/.config/polybar/modules/i3.ini
+
+# tray
+include-file = /home/danix/.config/polybar/modules/tray.ini
+
+# dunst-snooze
+include-file = /home/danix/.config/polybar/modules/dunst-snooze.ini
+
+# workspaces
+include-file = /home/danix/.config/polybar/modules/workspaces.ini
+
+# disks
+#include-file = /home/danix/.config/polybar/modules/filesys.ini
+
+# ram
+#include-file = /home/danix/.config/polybar/modules/memory.ini
+
+# cpu
+#include-file = /home/danix/.config/polybar/modules/cpu.ini
+
+# network
+#include-file = /home/danix/.config/polybar/modules/network.ini
+
+# slackware
+include-file = /home/danix/.config/polybar/modules/slackware.ini
+
+# kdeconnect
+# include-file = /home/danix/.config/polybar/modules/kdeconnect.ini
+
+# gmail-0
+include-file = /home/danix/.config/polybar/modules/gmail-0.ini
+
+# gmail-1
+include-file = /home/danix/.config/polybar/modules/gmail-1.ini
+
+# gmail-2
+include-file = /home/danix/.config/polybar/modules/gmail-2.ini
+
+# pulseaudio
+include-file = /home/danix/.config/polybar/modules/pulseaudio.ini
+
+# windows
+include-file = /home/danix/.config/polybar/modules/windows.ini
+
+# date
+include-file = /home/danix/.config/polybar/modules/date.ini
+
+# battery
+#include-file = /home/danix/.config/polybar/modules/battery.ini
+
+# weather
+include-file = /home/danix/.config/polybar/modules/weather.ini
+
+# keyboard
+include-file = /home/danix/.config/polybar/modules/keyboard.ini
+
+# windowlist
+include-file = /home/danix/.config/polybar/modules/windowlist.ini
+
diff --git a/polybar/config.ini-original b/polybar/config.ini-original
new file mode 100644 (file)
index 0000000..906e6cd
--- /dev/null
@@ -0,0 +1,423 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+;background = ${xrdb:color0:#222}
+background = #222
+background-alt = #444
+;foreground = ${xrdb:color7:#222}
+foreground = #dfdfdf
+foreground-alt = #555
+primary = #ffb52a
+secondary = #e60053
+alert = #bd2c40
+
+[bar/downbar]
+;monitor = ${env:MONITOR:HDMI-1}
+width = 100%
+height = 27
+;offset-x = 1%
+;offset-y = 1%
+radius = 6.0
+fixed-center = false
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3
+line-color = #f00
+
+border-size = 4
+border-color = #00000000
+
+padding-left = 0
+padding-right = 2
+
+module-margin-left = 1
+module-margin-right = 2
+
+font-0 = fixed:pixelsize=10;1
+font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
+font-2 = siji:pixelsize=10;1
+
+modules-left = bspwm i3
+modules-center = 
+modules-right = filesystem xbacklight alsa pulseaudio xkeyboard memory cpu wlan eth battery temperature date powermenu
+
+tray-position = right
+tray-padding = 2
+;tray-background = #0063ff
+
+;wm-restack = bspwm
+;wm-restack = i3
+
+;override-redirect = true
+
+;scroll-up = bspwm-desknext
+;scroll-down = bspwm-deskprev
+
+;scroll-up = i3wm-wsnext
+;scroll-down = i3wm-wsprev
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:30:...%
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-prefix-underline = ${colors.secondary}
+
+label-layout = %layout%
+label-layout-underline = ${colors.secondary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-background = ${colors.secondary}
+label-indicator-underline = ${colors.secondary}
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.foreground-alt}
+
+[module/bspwm]
+type = internal/bspwm
+
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+label-occupied = %index%
+label-occupied-padding = 2
+
+label-urgent = %index%!
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+label-empty = %index%
+label-empty-foreground = ${colors.foreground-alt}
+label-empty-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+[module/i3]
+type = internal/i3
+format = <label-state> <label-mode>
+index-sort = true
+wrapping-scroll = false
+
+; Only show workspaces on the same output as the bar
+;pin-workspaces = true
+
+label-mode-padding = 2
+label-mode-foreground = #000
+label-mode-background = ${colors.primary}
+
+; focused = Active workspace on focused monitor
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+; unfocused = Inactive workspace on any monitor
+label-unfocused = %index%
+label-unfocused-padding = 2
+
+; visible = Active workspace on unfocused monitor
+label-visible = %index%
+label-visible-background = ${self.label-focused-background}
+label-visible-underline = ${self.label-focused-underline}
+label-visible-padding = ${self.label-focused-padding}
+
+; urgent = Workspace with urgency hint set
+label-urgent = %index%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+
+[module/mpd]
+type = internal/mpd
+format-online = <label-song>  <icon-prev> <icon-stop> <toggle> <icon-next>
+
+icon-prev = 
+icon-stop = 
+icon-play = 
+icon-pause = 
+icon-next = 
+
+label-song-maxlen = 25
+label-song-ellipsis = true
+
+[module/xbacklight]
+type = internal/xbacklight
+
+format = <label> <bar>
+label = BL
+
+bar-width = 10
+bar-indicator = |
+bar-indicator-foreground = #fff
+bar-indicator-font = 2
+bar-fill = ─
+bar-fill-font = 2
+bar-fill-foreground = #9f78e1
+bar-empty = ─
+bar-empty-font = 2
+bar-empty-foreground = ${colors.foreground-alt}
+
+[module/backlight-acpi]
+inherit = module/xbacklight
+type = internal/backlight
+card = intel_backlight
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #f90000
+label = %percentage:2%%
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #4bffdc
+label = %percentage_used%%
+
+[module/wlan]
+type = internal/network
+interface = wlan0
+interval = 3.0
+
+format-connected = <ramp-signal> <label-connected>
+format-connected-underline = #9f78e1
+label-connected = %essid%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+ramp-signal-0 = 
+ramp-signal-1 = 
+ramp-signal-2 = 
+ramp-signal-3 = 
+ramp-signal-4 = 
+ramp-signal-foreground = ${colors.foreground-alt}
+
+[module/eth]
+type = internal/network
+interface = eth0
+interval = 3.0
+
+format-connected-underline = #55aa55
+format-connected-prefix = " "
+format-connected-prefix-foreground = ${colors.foreground-alt}
+label-connected = %local_ip%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+[module/date]
+type = internal/date
+interval = 5
+
+date =
+date-alt = " %Y-%m-%d"
+
+time = %H:%M
+time-alt = %H:%M:%S
+
+format-prefix = 
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #0a6cf5
+
+label = %date% %time%
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL %percentage%%
+label-volume-foreground = ${root.foreground}
+
+label-muted = 🔇 muted
+label-muted-foreground = #666
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/alsa]
+type = internal/alsa
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL
+label-volume-foreground = ${root.foreground}
+
+format-muted-prefix = " "
+format-muted-foreground = ${colors.foreground-alt}
+label-muted = sound muted
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/battery]
+type = internal/battery
+battery = BAT0
+adapter = AC0
+full-at = 98
+
+format-charging = <animation-charging> <label-charging>
+format-charging-underline = #ffb52a
+
+format-discharging = <animation-discharging> <label-discharging>
+format-discharging-underline = ${self.format-charging-underline}
+
+format-full-prefix = " "
+format-full-prefix-foreground = ${colors.foreground-alt}
+format-full-underline = ${self.format-charging-underline}
+
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-foreground = ${colors.foreground-alt}
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-foreground = ${colors.foreground-alt}
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-foreground = ${colors.foreground-alt}
+animation-discharging-framerate = 750
+
+[module/temperature]
+type = internal/temperature
+thermal-zone = 0
+warn-temperature = 60
+
+format = <ramp> <label>
+format-underline = #f50a4d
+format-warn = <ramp> <label-warn>
+format-warn-underline = ${self.format-underline}
+
+label = %temperature-c%
+label-warn = %temperature-c%
+label-warn-foreground = ${colors.secondary}
+
+ramp-0 = 
+ramp-1 = 
+ramp-2 = 
+ramp-foreground = ${colors.foreground-alt}
+
+[module/powermenu]
+type = custom/menu
+
+expand-right = true
+
+format-spacing = 1
+
+label-open = 
+label-open-foreground = ${colors.secondary}
+label-close =  cancel
+label-close-foreground = ${colors.secondary}
+label-separator = |
+label-separator-foreground = ${colors.foreground-alt}
+
+menu-0-0 = reboot
+menu-0-0-exec = menu-open-1
+menu-0-1 = power off
+menu-0-1-exec = menu-open-2
+
+menu-1-0 = cancel
+menu-1-0-exec = menu-open-0
+menu-1-1 = reboot
+menu-1-1-exec = sudo reboot
+
+menu-2-0 = power off
+menu-2-0-exec = sudo poweroff
+menu-2-1 = cancel
+menu-2-1-exec = menu-open-0
+
+[settings]
+screenchange-reload = true
+;compositing-background = xor
+;compositing-background = screen
+;compositing-foreground = source
+;compositing-border = over
+;pseudo-transparency = false
+
+[global/wm]
+margin-top = 5
+margin-bottom = 5
+
+; vim:ft=dosini
diff --git a/polybar/config.ini-vecchio b/polybar/config.ini-vecchio
new file mode 100644 (file)
index 0000000..809dac3
--- /dev/null
@@ -0,0 +1,383 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+;background = ${xrdb:color0:#222}
+background = #222
+background-alt = #444
+;foreground = ${xrdb:color7:#222}
+foreground = #dfdfdf
+foreground-alt = #555
+primary = #ffb52a
+secondary = #e60053
+alert = #bd2c40
+
+[bar/downbar]
+;monitor = ${env:MONITOR:HDMI-1}
+width = 100%
+height = 27
+;offset-x = 1%
+;offset-y = 1%
+radius = 6.0
+fixed-center = false
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3
+line-color = #f00
+
+border-size = 4
+border-color = #00000000
+
+padding-left = 0
+padding-right = 2
+
+module-margin-left = 1
+module-margin-right = 2
+
+font-0 = fixed:pixelsize=10;1
+font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
+font-2 = siji:pixelsize=10;1
+
+modules-left = i3
+modules-center = 
+modules-right = filesystem xbacklight pulseaudio xkeyboard memory cpu wlan eth battery temperature date powermenu
+
+tray-position = right
+tray-padding = 2
+;tray-background = #0063ff
+
+;wm-restack = i3
+
+;override-redirect = true
+
+scroll-up = i3wm-wsnext
+scroll-down = i3wm-wsprev
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:30:...%
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-prefix-underline = ${colors.secondary}
+
+label-layout = %layout%
+label-layout-underline = ${colors.secondary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-background = ${colors.secondary}
+label-indicator-underline = ${colors.secondary}
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.foreground-alt}
+
+[module/i3]
+type = internal/i3
+format = <label-state> <label-mode>
+index-sort = true
+wrapping-scroll = false
+
+; Only show workspaces on the same output as the bar
+;pin-workspaces = true
+
+label-mode-padding = 2
+label-mode-foreground = #000
+label-mode-background = ${colors.primary}
+
+; focused = Active workspace on focused monitor
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+; unfocused = Inactive workspace on any monitor
+label-unfocused = %index%
+label-unfocused-padding = 2
+
+; visible = Active workspace on unfocused monitor
+label-visible = %index%
+label-visible-background = ${self.label-focused-background}
+label-visible-underline = ${self.label-focused-underline}
+label-visible-padding = ${self.label-focused-padding}
+
+; urgent = Workspace with urgency hint set
+label-urgent = %index%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+[module/xbacklight]
+type = internal/xbacklight
+
+format = <label> <bar>
+label = BL
+
+bar-width = 10
+bar-indicator = |
+bar-indicator-foreground = #fff
+bar-indicator-font = 2
+bar-fill = ─
+bar-fill-font = 2
+bar-fill-foreground = #9f78e1
+bar-empty = ─
+bar-empty-font = 2
+bar-empty-foreground = ${colors.foreground-alt}
+
+[module/backlight-acpi]
+inherit = module/xbacklight
+type = internal/backlight
+card = intel_backlight
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #f90000
+label = %percentage:2%%
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #4bffdc
+label = %percentage_used%%
+
+[module/wlan]
+type = internal/network
+interface = wlan0
+interval = 3.0
+
+format-connected = <ramp-signal> <label-connected>
+format-connected-underline = #9f78e1
+label-connected = %essid%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+ramp-signal-0 = 
+ramp-signal-1 = 
+ramp-signal-2 = 
+ramp-signal-3 = 
+ramp-signal-4 = 
+ramp-signal-foreground = ${colors.foreground-alt}
+
+[module/eth]
+type = internal/network
+interface = eth0
+interval = 3.0
+
+format-connected-underline = #55aa55
+format-connected-prefix = " "
+format-connected-prefix-foreground = ${colors.foreground-alt}
+label-connected = %local_ip%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+[module/date]
+type = internal/date
+interval = 5
+
+date =
+date-alt = " %Y-%m-%d"
+
+time = %H:%M
+time-alt = %H:%M:%S
+
+format-prefix = 
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #0a6cf5
+
+label = %date% %time%
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL %percentage%%
+label-volume-foreground = ${root.foreground}
+
+label-muted = 🔇 muted
+label-muted-foreground = #666
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/alsa]
+type = internal/alsa
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL
+label-volume-foreground = ${root.foreground}
+
+format-muted-prefix = " "
+format-muted-foreground = ${colors.foreground-alt}
+label-muted = sound muted
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/battery]
+type = internal/battery
+battery = BAT0
+adapter = AC0
+full-at = 98
+
+format-charging = <animation-charging> <label-charging>
+format-charging-underline = #ffb52a
+
+format-discharging = <animation-discharging> <label-discharging>
+format-discharging-underline = ${self.format-charging-underline}
+
+format-full-prefix = " "
+format-full-prefix-foreground = ${colors.foreground-alt}
+format-full-underline = ${self.format-charging-underline}
+
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-foreground = ${colors.foreground-alt}
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-foreground = ${colors.foreground-alt}
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-foreground = ${colors.foreground-alt}
+animation-discharging-framerate = 750
+
+[module/temperature]
+type = internal/temperature
+thermal-zone = 0
+warn-temperature = 60
+
+format = <ramp> <label>
+format-underline = #f50a4d
+format-warn = <ramp> <label-warn>
+format-warn-underline = ${self.format-underline}
+
+label = %temperature-c%
+label-warn = %temperature-c%
+label-warn-foreground = ${colors.secondary}
+
+ramp-0 = 
+ramp-1 = 
+ramp-2 = 
+ramp-foreground = ${colors.foreground-alt}
+
+[module/powermenu]
+type = custom/menu
+
+expand-right = true
+
+format-spacing = 1
+
+label-open = 
+label-open-foreground = ${colors.secondary}
+label-close =  cancel
+label-close-foreground = ${colors.secondary}
+label-separator = |
+label-separator-foreground = ${colors.foreground-alt}
+
+menu-0-0 = reboot
+menu-0-0-exec = menu-open-1
+menu-0-1 = power off
+menu-0-1-exec = menu-open-2
+
+menu-1-0 = cancel
+menu-1-0-exec = menu-open-0
+menu-1-1 = reboot
+menu-1-1-exec = sudo reboot
+
+menu-2-0 = power off
+menu-2-0-exec = sudo poweroff
+menu-2-1 = cancel
+menu-2-1-exec = menu-open-0
+
+[settings]
+screenchange-reload = true
+;compositing-background = xor
+;compositing-background = screen
+;compositing-foreground = source
+;compositing-border = over
+;pseudo-transparency = false
+
+[global/wm]
+margin-top = 5
+margin-bottom = 5
+
+; vim:ft=dosini
diff --git a/polybar/config.ini.boh b/polybar/config.ini.boh
new file mode 100644 (file)
index 0000000..f6f6f74
--- /dev/null
@@ -0,0 +1,333 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+background = #00000000
+background-alt = #373B41
+foreground = #000319
+primary = #01ebea
+secondary = #0168eb
+alert = #f61717
+full = #01eb1c
+disabled = #c40082
+
+[bar/main]
+width = 100%
+height = 22pt
+radius = 0
+
+; dpi = 96
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 2pt
+
+;border-left-size = 20
+;border-right-size = 20
+;border-top-size = 7
+;border-color = #00000000
+
+padding-left = 0
+padding-right = 1
+
+module-margin = 1
+
+separator = |
+separator-foreground = ${colors.foreground}
+
+font-0 = "JetBrainsMonoNL Nerd Font:weight=bold;2"
+
+modules-left = xworkspaces
+modules-center = memory date cpu
+modules-right = pulseaudio-control-output wlan powerbutton logoutbutton battery
+# modules-disabled = filesystem xkeyboard xwindow eth pulseaudio 
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+enable-ipc = true
+
+
+[module/xworkspaces]
+type = internal/xworkspaces
+
+label-active = %name%
+label-active-background = ${colors.background}
+label-active-underline= ${colors.foreground}
+label-active-padding = 1
+
+label-occupied = %name%
+label-occupied-padding = 1
+
+label-urgent = %name%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 1
+
+label-empty = %name%
+label-empty-foreground = ${colors.disabled}
+label-empty-padding = 1
+
+#[module/xwindow]
+#type = internal/xwindow
+#label = %title:0:60:...%
+
+;[module/filesystem]
+;type = internal/fs
+;interval = 25
+
+;mount-0 = /
+
+;label-mounted = %{F#F0C674}%mountpoint%%{F-} %percentage_used%%
+
+;label-unmounted = %mountpoint% not mounted
+;label-unmounted-foreground = ${colors.disabled}
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume-prefix = "墳 "
+format-volume-prefix-foreground = ${colors.primary}
+format-volume = <label-volume>
+
+label-volume = %percentage%%
+
+label-muted = 婢 MUTED 
+label-muted-foreground = ${colors.disabled}
+
+#[module/xkeyboard]
+#type = internal/xkeyboard
+#blacklist-0 = num lock
+
+#label-layout = %layout%
+#label-layout-foreground = ${colors.primary}
+
+#label-indicator-padding = 2
+#label-indicator-margin = 1
+#label-indicator-foreground = ${colors.background}
+#label-indicator-background = ${colors.secondary}
+
+[module/pulseaudio-control-output]
+type = custom/script
+tail = true
+label-padding = 0
+label-foreground = ${colors.foreground}
+
+# Icons mixed from Font Awesome 5 and Material Icons
+# You can copy-paste your options for each possible action, which is more
+# trouble-free but repetitive, or apply only the relevant ones (for example
+# --node-blacklist is only needed for next-node).
+exec = pulseaudio-control --icons-volume " , " --icon-muted " " --node-nicknames-from "device.description" --node-nickname "alsa_output.pci-0000_00_1b.0.analog-stereo: " --node-nickname "alsa_output.usb-Kingston_HyperX_Virtual_Surround_Sound_00000000-00.analog-stereo: " listen
+click-right = exec pavucontrol &
+click-left = pulseaudio-control togmute
+click-middle = pulseaudio-control --node-blacklist "alsa_output.pci-0000_01_00.1.hdmi-stereo-extra2" next-node
+scroll-up = pulseaudio-control --volume-max 130 up
+scroll-down = pulseaudio-control --volume-max 130 down
+
+[module/pulseaudio-control-input]
+type = custom/script
+tail = true
+format-underline = ${colors.primary}
+label-padding = 2
+label-foreground = ${colors.primary}
+
+# Use --node-blacklist to remove the unwanted PulseAudio .monitor that are child of sinks
+exec = pulseaudio-control  --node-type input --icons-volume "" --icon-muted "" --node-nickname "alsa_output.pci-0000_0c_00.3.analog-stereo:  Webcam" --node-nickname "alsa_output.usb-Kingston_HyperX_Virtual_Surround_Sound_00000000-00.analog-stereo: Headphones" --node-blacklist "*.monitor" listen
+click-right = exec pavucontrol &
+click-left = pulseaudio-control --node-type input togmute
+click-middle = pulseaudio-control --node-type input next-node
+scroll-up = pulseaudio-control --node-type input --volume-max 130 up
+scroll-down = pulseaudio-control --node-type input --volume-max 130 down
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " " 
+format-prefix-foreground = ${colors.foreground}
+label = %percentage_used:2%%
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = "﬙ "
+format-prefix-foreground = ${colors.foreground}
+label = %percentage:2%%
+
+[network-base]
+type = internal/network
+interval = 5
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+label-disconnected = %{F#F0C674}%ifname%%{F#707880} disconnected
+
+[module/wlan]
+inherit = network-base
+interface-type = wireless
+label-connected = %{A1:nm-connection-editor:} %{A} 
+label-connected-foreground = ${colors.foreground}
+
+#[module/eth]
+#inherit = network-base
+#interface-type = wired
+#label-connected = %{F#F0C674}%ifname%%{F-} %local_ip%
+
+[module/powerbutton]
+type = custom/script
+exec = echo " "
+click-left = poweroff
+
+[module/logoutbutton]
+type = custom/script
+exec = echo "⏼ "
+click-left = i3-msg exit
+
+[module/date]
+type = internal/date
+interval = 1
+
+date =  %H:%M:%S
+date-alt =  %d|%m|%y
+
+label = %date%
+label-foreground = ${colors.foreground}
+
+[module/cava]
+type = custom/script
+tail = true
+exec = $HOME/.config/polybar/cava.sh
+format = <label>
+format-font = 5
+label = %output%
+label-foreground = #01ebea
+
+ [module/battery]
+type = internal/battery
+
+full-at = 100
+low-at = 15
+
+; Use the following command to list batteries and adapters:
+; $ ls -1 /sys/class/power_supply/
+battery = BAT0
+adapter = AC
+
+; Disable polling by setting the interval to 0.
+;
+; Default: 5
+poll-interval = 5
+
+; Available tags:
+;   <label-charging> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+;   <animation-charging>
+format-charging = <animation-charging>  <label-charging> 
+format-charging-foreground = ${colors.foreground}
+
+; Available tags:
+;   <label-discharging> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+;   <animation-discharging>
+format-discharging = <animation-discharging>  <label-discharging>
+format-discharging-foreground = ${colors.foreground}
+
+; Available tags:
+;   <label-full> (default)
+;   <bar-capacity>
+;   <ramp-capacity>
+format-full = <animation-charging>  <label-full>
+format-full-foreground = ${colors.foreground}
+
+; Format used when battery level drops to low-at
+; If not defined, format-discharging is used instead.
+; Available tags:
+;   <label-low>
+;   <animation-low>
+;   <bar-capacity>
+;   <ramp-capacity>
+; New in version 3.6.0
+format-low = <animation-discharging>  <label-low>
+format-low-foreground = ${colors.foreground}
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current charge rate in watts)
+label-charging = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current discharge rate in watts)
+label-discharging = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+label-full = %percentage%%
+
+; Available tokens:
+;   %percentage% (default) - is set to 100 if full-at is reached
+;   %percentage_raw%
+;   %time%
+;   %consumption% (shows current discharge rate in watts)
+; New in version 3.6.0
+label-low = %percentage%%
+
+; Only applies if <ramp-capacity> is used
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-3 = 
+ramp-capacity-4 = 
+
+; Only applies if <bar-capacity> is used
+bar-capacity-width = 10
+
+; Only applies if <animation-charging> is used
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-3 = 
+animation-charging-4 = 
+; Framerate in milliseconds
+animation-charging-framerate = 750
+
+; Only applies if <animation-discharging> is used
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-3 = 
+animation-discharging-4 = 
+; Framerate in milliseconds
+animation-discharging-framerate = 500
+
+; Only applies if <animation-low> is used
+; New in version 3.6.0
+animation-low-0 = !
+animation-low-1 = 
+animation-low-framerate = 200
+
+[settings]
+screenchange-reload = true
+pseudo-transparency = true
+
+; vim:ft=dosini
diff --git a/polybar/modules/airquality.ini b/polybar/modules/airquality.ini
new file mode 100644 (file)
index 0000000..52c0962
--- /dev/null
@@ -0,0 +1,5 @@
+[module/info-airqualityindex]
+type = custom/script
+exec = ~/bin/info-airqualityindex.sh
+interval = 600
+
diff --git a/polybar/modules/battery.ini b/polybar/modules/battery.ini
new file mode 100644 (file)
index 0000000..33b7882
--- /dev/null
@@ -0,0 +1,39 @@
+[module/battery]
+type = internal/battery
+
+battery = BAT0
+adapter = AC0
+
+full-at = 95
+low-at = 15
+poll-interval = 5
+format-charging = <animation-charging>  <label-charging> 
+format-charging-foreground = ${colors.foreground}
+
+format-discharging = <animation-discharging>  <label-discharging>
+format-discharging-foreground = ${colors.foreground}
+
+format-full = <label-full>
+format-full-foreground = ${colors.full}
+
+format-low = <label-low>
+format-low-foreground = ${colors.alert}
+
+label-charging =  %percentage%%
+label-discharging = %percentage%%
+label-full =   %percentage%%
+label-low =   %percentage%%
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-3 = 
+animation-charging-4 = 
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-3 = 
+animation-discharging-4 = 
+animation-discharging-framerate = 750
diff --git a/polybar/modules/cpu.ini b/polybar/modules/cpu.ini
new file mode 100644 (file)
index 0000000..a459215
--- /dev/null
@@ -0,0 +1,15 @@
+[module/cpu]
+type = internal/cpu
+interval = 0.5
+warn-percentage = 95
+format = <label> <ramp-coreload>
+label = 󰻠 %percentage%%
+ramp-coreload-spacing = 1px
+ramp-coreload-0 = ▁
+ramp-coreload-1 = ▂
+ramp-coreload-2 = ▃
+ramp-coreload-3 = ▄
+ramp-coreload-4 = ▅
+ramp-coreload-5 = ▆
+ramp-coreload-6 = ▇
+ramp-coreload-7 = █
diff --git a/polybar/modules/date.ini b/polybar/modules/date.ini
new file mode 100644 (file)
index 0000000..528a44d
--- /dev/null
@@ -0,0 +1,8 @@
+[module/date]
+type = internal/date
+
+interval = 1
+date =   %H:%M:%S
+date-alt =  %d/%m/%Y
+label = %{A3:qarma --calendar:} %date% %{A}
+label-foreground = ${colors.foreground}
diff --git a/polybar/modules/duckstation.ini b/polybar/modules/duckstation.ini
new file mode 100644 (file)
index 0000000..1e805f7
--- /dev/null
@@ -0,0 +1,10 @@
+[module/duckstation]
+type = custom/text
+click-left = /usr/bin/duckstation-qt
+
+format = <label>
+format-foreground = ${colors.foreground}
+format-padding = 2
+
+label = ""
+
diff --git a/polybar/modules/dunst-snooze.ini b/polybar/modules/dunst-snooze.ini
new file mode 100644 (file)
index 0000000..01567d2
--- /dev/null
@@ -0,0 +1,6 @@
+[module/dunst-snooze]
+type = custom/script
+exec = ~/bin/dunst-snooze.sh
+interval = 2
+click-left = ~/bin/dunst-snooze.sh --toggle &
+
diff --git a/polybar/modules/filesys.ini b/polybar/modules/filesys.ini
new file mode 100644 (file)
index 0000000..073e76e
--- /dev/null
@@ -0,0 +1,18 @@
+[fs-base]
+type = internal/fs
+fixed-values = true
+
+[module/fs-root]
+inherit = fs-base
+mount-0 = /
+label-mounted =  /: %percentage_used%%
+
+[module/fs-home]
+inherit = fs-base
+mount-0 = /home
+label-mounted =  /~: %percentage_used%%
+
+[module/fs-data]
+inherit = fs-base
+mount-0 = /data
+label-mounted =  /data: %percentage_used%%
diff --git a/polybar/modules/gmail-0.ini b/polybar/modules/gmail-0.ini
new file mode 100644 (file)
index 0000000..20d393e
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-0]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c f61717 -cr ~/.config/polybar/modules/gmail/credentials_danixland.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/0/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/polybar/modules/gmail-1.ini b/polybar/modules/gmail-1.ini
new file mode 100644 (file)
index 0000000..a0e68e1
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-1]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c 01eb1c -cr ~/.config/polybar/modules/gmail/credentials_itdm.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/2/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/polybar/modules/gmail-2.ini b/polybar/modules/gmail-2.ini
new file mode 100644 (file)
index 0000000..0099eab
--- /dev/null
@@ -0,0 +1,10 @@
+[module/gmail-2]
+type = custom/script
+exec = python3 ~/.config/polybar/modules/gmail/launch.py -p  -c 0111eb -cr ~/.config/polybar/modules/gmail/credentials_65d85.json
+tail = true
+click-left = xdg-open https://mail.google.com/mail/u/1/#search/is%3Aunread
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/polybar/modules/gmail/.gitignore b/polybar/modules/gmail/.gitignore
new file mode 100644 (file)
index 0000000..83f6e39
--- /dev/null
@@ -0,0 +1 @@
+credentials.json
diff --git a/polybar/modules/gmail/LICENSE b/polybar/modules/gmail/LICENSE
new file mode 100644 (file)
index 0000000..ef9d518
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2022 Vyacheslav Konovalov https://github.com/crabvk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/polybar/modules/gmail/README.md b/polybar/modules/gmail/README.md
new file mode 100644 (file)
index 0000000..dc4a191
--- /dev/null
@@ -0,0 +1,67 @@
+# Polybar Gmail
+
+A [Polybar](https://github.com/jaagr/polybar) module to show unread messages from Gmail.
+
+![preview](https://github.com/crabvk/polybar-gmail/raw/master/preview.png)
+
+## Dependencies
+
+```sh
+pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
+# or use poetry
+```
+
+**Font Awesome** - default email icon
+
+**canberra-gtk-play** - new email sound notification
+
+You can change the icon or turn off sound, for more info see [script arguments](#script-arguments)
+
+## Installation
+
+```sh
+cd ~/.config/polybar
+curl -LO https://github.com/crabvk/polybar-gmail/archive/master.tar.gz
+tar zxf master.tar.gz && rm master.tar.gz
+mv polybar-gmail-master gmail
+```
+
+and obtain/refresh credentials
+
+```sh
+~/.config/polybar/gmail/auth.py
+```
+
+### Module
+
+```ini
+[module/gmail]
+type = custom/script
+exec = ~/.config/polybar/gmail/launch.py
+tail = true
+click-left = xdg-open https://mail.google.com
+```
+
+## Script arguments
+
+`-l` or `--label` - set user's mailbox [label](https://developers.google.com/gmail/api/v1/reference/users/labels/list), default: INBOX
+
+`-p` or `--prefix` - set email icon, default: 
+
+`-c` or `--color` - set new email icon color, default: #e06c75
+
+`-ns` or `--nosound` - turn off new email sound
+
+`-cr` or `--credentials` - path to your `credentials.json`, defaults to `credentials.json`
+
+### Example
+
+```sh
+./launch.py --label 'CATEGORY_PERSONAL' --prefix '✉' --color '#be5046' --nosound
+```
+
+## Get list of all your mailbox labels
+
+```python
+./list_labels.py
+```
diff --git a/polybar/modules/gmail/auth.py b/polybar/modules/gmail/auth.py
new file mode 100755 (executable)
index 0000000..2923b23
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from pathlib import Path
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from google.auth.transport.requests import Request
+
+SCOPES = 'https://www.googleapis.com/auth/gmail.labels'
+DIR = Path(__file__).resolve().parent
+CLIENT_SECRETS_PATH = Path(DIR, 'client_secrets.json')
+CREDENTIALS_PATH = Path(DIR, 'credentials.json')
+
+if Path(CREDENTIALS_PATH).is_file():
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+    if creds.expired and creds.refresh_token:
+        creds.refresh(Request())
+    else:
+        print('Credentials look OK, try to remove credentials.json if something doesn\'t work')
+        exit()
+else:
+    flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_PATH, scopes=SCOPES)
+    creds = flow.run_local_server(open_browser=False)
+
+# Save credentials
+with open(CREDENTIALS_PATH, 'w') as creds_file:
+    creds_file.write(creds.to_json())
+print('Credentials saved successfully')
diff --git a/polybar/modules/gmail/client_secrets.json b/polybar/modules/gmail/client_secrets.json
new file mode 100644 (file)
index 0000000..460b0e7
--- /dev/null
@@ -0,0 +1 @@
+{"installed":{"client_id":"1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com","project_id":"polybar-gmail","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"-aZZAslLp6ydldCAFvH9AEwi","redirect_uris":["http://localhost"]}}
\ No newline at end of file
diff --git a/polybar/modules/gmail/credentials_65d85.json b/polybar/modules/gmail/credentials_65d85.json
new file mode 100644 (file)
index 0000000..f39e67c
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVsp5UtITw6_bgZG5G0YB4yFf8ko2V-vc4Js2tP8wfuTChmkomY_AB0nmGNlsYHkRkYPiFFI-_PQtMZb0rxExdwCZ1y8UPWd85LN8pB4t0r2eMSTyGMpsXcnemmRhbtvp9OtSzQcB1eV3u91Vsb4NjpKlaCgYKAW0SARASFQGbdwaIuHXcRdBYC8Q2Bm5Bj8OzWA0163", "refresh_token": "1//09NrIslEcmjmBCgYIARAAGAkSNwF-L9IrDYd9hkTTPBNdHzP4ORlqCis5okzJHjuYqXZMVtOZArRlIESS41FLAFOPu9xbjlNpkNA", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:34:08.969897Z"}
\ No newline at end of file
diff --git a/polybar/modules/gmail/credentials_danixland.json b/polybar/modules/gmail/credentials_danixland.json
new file mode 100644 (file)
index 0000000..69300a4
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVsp32kYJwSDjvLnTP6XTPJjbPwVpdSqxseKZ093yBCtyrrcxlh-0YTU-FDrAA2xPj6qTPCLPyYW-mBXzGzSNF6h6Cjycbn4r5BV_cEYqiDtqp1TQnMrShQYSIsM2Mh405hjRfxoi48auwk9RWQJeFjfvaCgYKAXISARESFQGbdwaINWUUHQ96gBXo8jQhSFTQUw0163", "refresh_token": "1//09rAkNYR3IAN7CgYIARAAGAkSNwF-L9IrwgIPuApwQp3-s1sd_LjpWvNZN_6J6UcK8yj3mx7mXvt3n1evnLgGYOy8aF7AbRPwJqI", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:18:57.969451Z"}
\ No newline at end of file
diff --git a/polybar/modules/gmail/credentials_itdm.json b/polybar/modules/gmail/credentials_itdm.json
new file mode 100644 (file)
index 0000000..73ffee9
--- /dev/null
@@ -0,0 +1 @@
+{"token": "ya29.a0AVvZVspE_K6QiraMg24MrOJvWIUnXtPjGQOoqTl8GrVd2gmdEMPpK55gJKQ_6OhZwx_eBI_P8YEv6F6aXLD8w5AGd-OY7lclbGOYp__ig9zEQJQzEcGvl0v9kVWyMnbmuwemry51NTY1aJU65rOm3I5FCrO1aCgYKAa4SARASFQGbdwaIfewk3tYDvSp9Eofoz03UhQ0163", "refresh_token": "1//09W5c9oMuaMS-CgYIARAAGAkSNwF-L9IrNiQdC25DOhbiyxvxJkK5SdjpgYq_hmSBUhlInBNGJ11d99NW5p3HG9E_m5F5SAN293I", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "1041679298587-8solnkr9tr8iktrut958if6tsgqt42m2.apps.googleusercontent.com", "client_secret": "-aZZAslLp6ydldCAFvH9AEwi", "scopes": "https://www.googleapis.com/auth/gmail.labels", "expiry": "2023-02-04T13:32:27.200554Z"}
\ No newline at end of file
diff --git a/polybar/modules/gmail/launch.py b/polybar/modules/gmail/launch.py
new file mode 100755 (executable)
index 0000000..99f85a7
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+import time
+import argparse
+import subprocess
+from pathlib import Path
+from googleapiclient import discovery, errors
+from google.oauth2.credentials import Credentials
+from google.auth.exceptions import TransportError
+from httplib2 import ServerNotFoundError
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-l', '--label', default='INBOX')
+parser.add_argument('-p', '--prefix', default='\uf0e0')
+parser.add_argument('-c', '--color', default='#e06c75')
+parser.add_argument('-ns', '--nosound', action='store_true')
+parser.add_argument('-cr', '--credentials', default='credentials.json')
+args = parser.parse_args()
+
+DIR = Path(__file__).resolve().parent
+CREDENTIALS_PATH = Path(DIR, args.credentials)
+
+unread_prefix = '%{F' + args.color + '}' + args.prefix + ' %{F-}'
+error_prefix = '%{F' + args.color + '}\uf06a %{F-}'
+count_was = 0
+
+
+def print_count(count, is_odd=False):
+    tilde = '~' if is_odd else ''
+    output = ''
+    if count > 0:
+        output = unread_prefix + tilde + str(count)
+    else:
+        output = (args.prefix + ' ' + tilde).strip()
+    print(output, flush=True)
+
+
+def update_count(count_was):
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+    gmail = discovery.build('gmail', 'v1', credentials=creds)
+    labels = gmail.users().labels().get(userId='me', id=args.label).execute()
+    count = labels['messagesUnread']
+    print_count(count)
+    if not args.nosound and count_was < count and count > 0:
+        subprocess.run(['canberra-gtk-play', '-i', 'message'])
+    return count
+
+
+print_count(0, True)
+
+while True:
+    try:
+        if Path(CREDENTIALS_PATH).is_file():
+            count_was = update_count(count_was)
+            time.sleep(10)
+        else:
+            print(error_prefix + 'credentials not found', flush=True)
+            time.sleep(2)
+    except errors.HttpError as error:
+        if error.resp.status == 404:
+            print(error_prefix + f'"{args.label}" label not found', flush=True)
+        else:
+            print_count(count_was, True)
+        time.sleep(5)
+    except (ServerNotFoundError, OSError, TransportError):
+        print_count(count_was, True)
+        time.sleep(5)
diff --git a/polybar/modules/gmail/list_labels.py b/polybar/modules/gmail/list_labels.py
new file mode 100755 (executable)
index 0000000..4118623
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+import os
+import sys
+from googleapiclient.discovery import build
+from google.oauth2.credentials import Credentials
+
+DIR = os.path.dirname(os.path.realpath(__file__))
+CREDENTIALS_PATH = os.path.join(DIR, 'credentials.json')
+
+try:
+    creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH)
+except FileNotFoundError:
+    sys.exit('File ' + CREDENTIALS_PATH + ' not found. Run auth.py to obtain credentials.')
+gmail = build('gmail', 'v1', credentials=creds)
+labels = gmail.users().labels().list(userId='me').execute()
+for label in labels['labels']:
+    print(label['id'])
diff --git a/polybar/modules/gmail/poetry.lock b/polybar/modules/gmail/poetry.lock
new file mode 100644 (file)
index 0000000..f9ba2d3
--- /dev/null
@@ -0,0 +1,412 @@
+[[package]]
+name = "autopep8"
+version = "2.0.0"
+description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycodestyle = ">=2.9.1"
+tomli = "*"
+
+[[package]]
+name = "cachetools"
+version = "5.2.0"
+description = "Extensible memoizing collections and decorators"
+category = "main"
+optional = false
+python-versions = "~=3.7"
+
+[[package]]
+name = "certifi"
+version = "2022.9.24"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.1.1"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.6.0"
+
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
+[[package]]
+name = "google-api-core"
+version = "2.10.2"
+description = "Google API client core library"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+google-auth = ">=1.25.0,<3.0dev"
+googleapis-common-protos = ">=1.56.2,<2.0dev"
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+requests = ">=2.18.0,<3.0.0dev"
+
+[package.extras]
+grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"]
+grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
+grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
+
+[[package]]
+name = "google-api-python-client"
+version = "2.66.0"
+description = "Google API Client Library for Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+google-api-core = ">=1.31.5,<2.0.0 || >2.3.0,<3.0.0dev"
+google-auth = ">=1.19.0,<3.0.0dev"
+google-auth-httplib2 = ">=0.1.0"
+httplib2 = ">=0.15.0,<1dev"
+uritemplate = ">=3.0.1,<5"
+
+[[package]]
+name = "google-auth"
+version = "2.14.1"
+description = "Google Authentication Library"
+category = "main"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
+
+[package.dependencies]
+cachetools = ">=2.0.0,<6.0"
+pyasn1-modules = ">=0.2.1"
+rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""}
+six = ">=1.9.0"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"]
+enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"]
+pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"]
+reauth = ["pyu2f (>=0.1.5)"]
+
+[[package]]
+name = "google-auth-httplib2"
+version = "0.1.0"
+description = "Google Authentication Library: httplib2 transport"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+google-auth = "*"
+httplib2 = ">=0.15.0"
+six = "*"
+
+[[package]]
+name = "google-auth-oauthlib"
+version = "0.7.1"
+description = "Google Authentication Library"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+google-auth = ">=2.14.0"
+requests-oauthlib = ">=0.7.0"
+
+[package.extras]
+tool = ["click (>=6.0.0)"]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.57.0"
+description = "Common protobufs used in Google APIs"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+
+[package.extras]
+grpc = ["grpcio (>=1.44.0,<2.0.0dev)"]
+
+[[package]]
+name = "httplib2"
+version = "0.21.0"
+description = "A comprehensive HTTP client library."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "oauthlib"
+version = "3.2.2"
+description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+rsa = ["cryptography (>=3.0.0)"]
+signals = ["blinker (>=1.4.0)"]
+signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
+
+[[package]]
+name = "protobuf"
+version = "4.21.9"
+description = ""
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "pyasn1"
+version = "0.4.8"
+description = "ASN.1 types and codecs"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.2.8"
+description = "A collection of ASN.1-based protocols modules."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pyasn1 = ">=0.4.6,<0.5.0"
+
+[[package]]
+name = "pycodestyle"
+version = "2.9.1"
+description = "Python style guide checker"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "main"
+optional = false
+python-versions = ">=3.6.8"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "requests"
+version = "2.28.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=3.7, <4"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<3"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "requests-oauthlib"
+version = "1.3.1"
+description = "OAuthlib authentication support for Requests."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+oauthlib = ">=3.0.0"
+requests = ">=2.0.0"
+
+[package.extras]
+rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
+
+[[package]]
+name = "rsa"
+version = "4.9"
+description = "Pure-Python RSA implementation"
+category = "main"
+optional = false
+python-versions = ">=3.6,<4"
+
+[package.dependencies]
+pyasn1 = ">=0.1.3"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "uritemplate"
+version = "4.1.1"
+description = "Implementation of RFC 6570 URI Templates"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.10.7"
+content-hash = "ce33e021036b36c94cf6045952262c7f1d9d7287c4d3778ac9bf211b90ad2e2e"
+
+[metadata.files]
+autopep8 = [
+    {file = "autopep8-2.0.0-py2.py3-none-any.whl", hash = "sha256:ad924b42c2e27a1ac58e432166cc4588f5b80747de02d0d35b1ecbd3e7d57207"},
+    {file = "autopep8-2.0.0.tar.gz", hash = "sha256:8b1659c7f003e693199f52caffdc06585bb0716900bbc6a7442fd931d658c077"},
+]
+cachetools = [
+    {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"},
+    {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"},
+]
+certifi = [
+    {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+    {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+    {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
+google-api-core = [
+    {file = "google-api-core-2.10.2.tar.gz", hash = "sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320"},
+    {file = "google_api_core-2.10.2-py3-none-any.whl", hash = "sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e"},
+]
+google-api-python-client = [
+    {file = "google-api-python-client-2.66.0.tar.gz", hash = "sha256:4cfaf0205aa7c538c8fb1772368be3d049dfed7886adf48597e9a766e9828a6e"},
+    {file = "google_api_python_client-2.66.0-py2.py3-none-any.whl", hash = "sha256:3b45110b638232959f75418231dfb487228102a4a91a7a3e64147684befaebee"},
+]
+google-auth = [
+    {file = "google-auth-2.14.1.tar.gz", hash = "sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d"},
+    {file = "google_auth-2.14.1-py2.py3-none-any.whl", hash = "sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016"},
+]
+google-auth-httplib2 = [
+    {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"},
+    {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
+]
+google-auth-oauthlib = [
+    {file = "google-auth-oauthlib-0.7.1.tar.gz", hash = "sha256:9940f543f77d1447432a93781d7c931fb53e418023351ad4bf9e92837a1154ec"},
+    {file = "google_auth_oauthlib-0.7.1-py2.py3-none-any.whl", hash = "sha256:860e54c4b58b2664116c9cb44325bc0ec92bcd93e8211698ceea911b1b873b86"},
+]
+googleapis-common-protos = [
+    {file = "googleapis-common-protos-1.57.0.tar.gz", hash = "sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46"},
+    {file = "googleapis_common_protos-1.57.0-py2.py3-none-any.whl", hash = "sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c"},
+]
+httplib2 = [
+    {file = "httplib2-0.21.0-py3-none-any.whl", hash = "sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01"},
+    {file = "httplib2-0.21.0.tar.gz", hash = "sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34"},
+]
+idna = [
+    {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+    {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+oauthlib = [
+    {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
+    {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
+]
+protobuf = [
+    {file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"},
+    {file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"},
+    {file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"},
+    {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"},
+    {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"},
+    {file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"},
+    {file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"},
+    {file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"},
+    {file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"},
+    {file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"},
+    {file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"},
+    {file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"},
+    {file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"},
+    {file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"},
+]
+pyasn1 = [
+    {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
+    {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
+]
+pyasn1-modules = [
+    {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
+    {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
+]
+pycodestyle = [
+    {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
+    {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
+]
+pyparsing = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+requests = [
+    {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+    {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+]
+requests-oauthlib = [
+    {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
+    {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
+]
+rsa = [
+    {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
+    {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
+]
+six = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+tomli = [
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+uritemplate = [
+    {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
+    {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+    {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+]
diff --git a/polybar/modules/gmail/preview.png b/polybar/modules/gmail/preview.png
new file mode 100644 (file)
index 0000000..57062f2
Binary files /dev/null and b/polybar/modules/gmail/preview.png differ
diff --git a/polybar/modules/gmail/pyproject.toml b/polybar/modules/gmail/pyproject.toml
new file mode 100644 (file)
index 0000000..ff15d27
--- /dev/null
@@ -0,0 +1,22 @@
+[tool.poetry]
+name = "polybar-gmail"
+version = "0.2.1"
+description = "Polybar module to show unread messages from Gmail"
+authors = ["Vyacheslav Konovalov <crabvk@protonmail.com>"]
+license = "MIT"
+
+[tool.poetry.dependencies]
+python = "^3.10.7"
+google-api-python-client = "^2.66.0"
+google-auth-httplib2 = "^0.1.0"
+google-auth-oauthlib = "^0.7.1"
+autopep8 = "^2.0.0"
+
+[tool.poetry.dev-dependencies]
+
+[build-system]
+requires = ["poetry>=1.1.0"]
+build-backend = "poetry.masonry.api"
+
+[tool.autopep8]
+max_line_length = 100
diff --git a/polybar/modules/i3.ini b/polybar/modules/i3.ini
new file mode 100644 (file)
index 0000000..175ab6b
--- /dev/null
@@ -0,0 +1,34 @@
+[module/i3]
+type = internal/i3
+pin-workspaces = true
+show-urgent = true
+strip-wsnumbers = true
+enable-click = true
+enable-scroll = true
+wrapping-scroll = true
+
+ws-icon-0 = web;󰖟
+ws-icon-1 = editor;
+ws-icon-2 = console;󰆍
+ws-icon-3 = ssh;󰢩
+ws-icon-4 = graphic;󰽉
+ws-icon-5 = editor2;󰼭
+ws-icon-6 = chat;󰭻
+ws-icon-7 = music;
+ws-icon-default = 
+
+label-focused = %icon%
+label-focused-padding = 1
+label-focused-underline = ${colors.secondary}
+
+label-unfocused = %icon%
+
+label-visible = %icon%
+label-visible-underline = ${colors.secondary}
+
+label-urgent = %icon%
+label-urgent-underline = ${colors.alert}
+
+label-separator = |
+label-separator-padding = 1
+label-separator-foreground = ${colors.foreground}
diff --git a/polybar/modules/kdeconnect.ini b/polybar/modules/kdeconnect.ini
new file mode 100644 (file)
index 0000000..3691740
--- /dev/null
@@ -0,0 +1,5 @@
+[module/kdeconnect]  
+type = custom/script  
+exec = polybar-kdeconnect.sh -d
+tail = true
+interval = 600
diff --git a/polybar/modules/keyboard.ini b/polybar/modules/keyboard.ini
new file mode 100644 (file)
index 0000000..51fd4ac
--- /dev/null
@@ -0,0 +1,20 @@
+[module/xkeyboard]
+type = internal/xkeyboard
+#xkeyboard.switch
+format-spacing = 1
+label-layout = %layout%
+label-layout-foreground = ${colors.foreground}
+label-indicator-padding = 1
+label-indicator-margin = 0
+label-indicator-foreground = ${colors.foreground}
+
+indicator-icon-0 = caps lock;;󰪛
+indicator-icon-1 = scroll lock;;󰹺
+indicator-icon-2 = num lock;;󰼎
+
+label-indicator-on-capslock = %icon%
+label-indicator-off-capslock = %icon%
+label-indicator-on-numlock = %icon%
+label-indicator-off-numlock = %icon%
+label-indicator-on-scrolllock = %icon%
+label-indicator-off-scrolllock = %icon%
\ No newline at end of file
diff --git a/polybar/modules/memory.ini b/polybar/modules/memory.ini
new file mode 100644 (file)
index 0000000..877d948
--- /dev/null
@@ -0,0 +1,6 @@
+[module/memory]
+type = internal/memory
+interval = 3
+warn-percentage = 95
+format = <label>
+label = 󰍛 %percentage_used%%
diff --git a/polybar/modules/network.ini b/polybar/modules/network.ini
new file mode 100644 (file)
index 0000000..efa4e18
--- /dev/null
@@ -0,0 +1,16 @@
+[module/network]
+type = internal/network
+
+interface = eth0
+interface-type = wired
+interval = 3.0
+speed-unit = 'KB/s'
+
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+
+
+label-connected = 󰖩  %downspeed:5% / %upspeed:5%
+label-connected-foreground = ${colors.secondary}
+label-disconnected = 󰤮
+label-disconnected-foreground = ${colors.alert}
diff --git a/polybar/modules/pulseaudio.ini b/polybar/modules/pulseaudio.ini
new file mode 100644 (file)
index 0000000..b33f4e8
--- /dev/null
@@ -0,0 +1,13 @@
+[module/pulseaudio]
+type = internal/pulseaudio
+format-volume-prefix = "墳 "
+#format-volume-prefix-background = ${colors.background}
+format-volume-prefix-foreground = ${colors.foreground}
+format-volume = <label-volume>
+format-volume-padding = 2
+#format-volume-background = ${colors.background}
+label-volume = %percentage%%
+label-muted = 婢 SHHH! 
+label-muted-foreground = ${colors.secondary}
+#label-muted-background = ${colors.background}
+label-muted-padding = 2
diff --git a/polybar/modules/slackware.ini b/polybar/modules/slackware.ini
new file mode 100644 (file)
index 0000000..5d27393
--- /dev/null
@@ -0,0 +1,10 @@
+[module/slackware]
+type = custom/script
+exec = slack-updates
+interval = 600
+click-left = slack-updates -n
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/polybar/modules/temperature.ini b/polybar/modules/temperature.ini
new file mode 100644 (file)
index 0000000..3bb0621
--- /dev/null
@@ -0,0 +1,54 @@
+[module/temperature]
+type = internal/temperature
+
+; Seconds to sleep between updates
+; Default: 1
+interval = 5
+
+; display units
+units = true
+
+; Thermal zone to use
+; To list all the zone types, run 
+; $ for i in /sys/class/thermal/thermal_zone*; do echo "$i: $(<$i/type)"; done
+; Default: 0
+thermal-zone = 6
+
+; Full path of temperature sysfs path
+; Use `sensors` to find preferred temperature source, then run
+; $ for i in /sys/class/hwmon/hwmon*/temp*_input; do echo "$(<$(dirname $i)/name): $(cat ${i%_*}_label 2>/dev/null || echo $(basename ${i%_*})) $(readlink -f $i)"; done
+; to find path to desired file
+; Default reverts to thermal zone setting
+; hwmon-path = /sys/devices/platform/coretemp.0/hwmon/hwmon4/temp1_input
+hwmon-path = /sys/devices/virtual/thermal/thermal_zone6/hwmon2/temp1_input
+
+; Base temperature for where to start the ramp (in degrees celsius)
+; Default: 0
+base-temperature = 20
+
+; Threshold temperature to display warning label (in degrees celsius)
+; Default: 80
+warn-temperature = 80
+
+; Available tags:
+;   <label> (default)
+;   <ramp>
+format = <label>
+
+; Available tags:
+;   <label-warn> (default)
+;   <ramp>
+format-warn = <label-warn>
+
+; Available tokens:
+;   %temperature% (deprecated)
+;   %temperature-c%   (default, temperature in °C)
+;   %temperature-f%   (temperature in °F)
+label = TEMP %temperature-c%
+
+; Available tokens:
+;   %temperature% (deprecated)
+;   %temperature-c%   (default, temperature in °C)
+;   %temperature-f%   (temperature in °F)
+label-warn = TEMP %temperature-c%
+label-warn-foreground = #f00
\ No newline at end of file
diff --git a/polybar/modules/tray.ini b/polybar/modules/tray.ini
new file mode 100644 (file)
index 0000000..f496790
--- /dev/null
@@ -0,0 +1,6 @@
+[module/tray]
+type = internal/tray
+
+tray-padding = 2px
+tray-spacing = 2px
+tray-size = 90%
diff --git a/polybar/modules/weather.ini b/polybar/modules/weather.ini
new file mode 100644 (file)
index 0000000..eb7f59b
--- /dev/null
@@ -0,0 +1,10 @@
+[module/weather]
+type = custom/script
+exec = ~/.config/polybar/modules/weather/openweathermap-simple.sh
+interval = 600
+click-left = xdg-open https://openweathermap.org/city/3164527
+
+format = <label>
+#format-background = ${colors.background}
+format-foreground = ${colors.foreground}
+format-padding = 2
diff --git a/polybar/modules/weather/openweathermap-simple.sh b/polybar/modules/weather/openweathermap-simple.sh
new file mode 100755 (executable)
index 0000000..965d8b3
--- /dev/null
@@ -0,0 +1,329 @@
+#!/bin/bash
+
+# SETTINGS vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+
+# API settings ________________________________________________________________
+
+APIKEY='aecd76a64e7f34dd2aae9249ca5d1304'
+# if you leave these empty location will be picked based on your ip-adres
+CITY_NAME='Verona'
+COUNTRY_CODE='IT'
+# Desired output language
+LANG="en"
+# UNITS can be "metric", "imperial" or "kelvin". Set KNOTS to "yes" if you
+# want the wind in knots:
+
+#          | temperature | wind
+# -----------------------------------
+# metric   | Celsius     | km/h
+# imperial | Fahrenheit  | miles/hour
+# kelvin   | Kelvin      | km/h
+
+UNITS="metric"
+
+# Color Settings ______________________________________________________________
+
+COLOR_CLOUD="#606060"
+COLOR_THUNDER="#d3b987"
+COLOR_LIGHT_RAIN="#73cef4"
+COLOR_HEAVY_RAIN="#b3deef"
+COLOR_SNOW="#FFFFFF"
+COLOR_FOG="#606060"
+COLOR_TORNADO="#d3b987"
+COLOR_SUN="#ffc24b"
+COLOR_MOON="#FFFFFF"
+COLOR_ERR="#f43753"
+COLOR_WIND="#73cef4"
+COLOR_COLD="#b3deef"
+COLOR_HOT="#f43753"
+COLOR_NORMAL_TEMP="#FFFFFF"
+
+# Leave "" if you want the default polybar color
+COLOR_TEXT=""
+# Polybar settings ____________________________________________________________
+
+# Font for the weather icons
+WEATHER_FONT_CODE=4
+
+# Font for the thermometer icon
+TEMP_FONT_CODE=2
+
+# Wind settings _______________________________________________________________
+
+# Display info about the wind or not. yes/no
+DISPLAY_WIND="yes"
+
+# Show beaufort level in windicon
+BEAUFORTICON="yes"
+
+# Display in knots. yes/no
+KNOTS="yes"
+
+# How many decimals after the floating point
+DECIMALS=0
+
+# Min. wind force required to display wind info (it depends on what
+# measurement unit you have set: knots, m/s or mph). Set to 0 if you always
+# want to display wind info. It's ignored if DISPLAY_WIND is false.
+
+MIN_WIND=11
+
+# Display the numeric wind force or not. If not, only the wind icon will
+# appear. yes/no
+
+DISPLAY_FORCE="yes"
+
+# Display the wind unit if wind force is displayed. yes/no
+DISPLAY_WIND_UNIT="yes"
+
+# Thermometer settings ________________________________________________________
+
+# When the thermometer icon turns red
+HOT_TEMP=25
+
+# When the thermometer icon turns blue
+COLD_TEMP=0
+
+# Other settings ______________________________________________________________
+
+# Display the weather description. yes/no
+DISPLAY_LABEL="no"
+
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+if [ "$COLOR_TEXT" != "" ]; then
+    COLOR_TEXT_BEGIN="%{F$COLOR_TEXT}"
+    COLOR_TEXT_END="%{F-}"
+fi
+if [ -z "$CITY_NAME" ]; then
+    IP=`curl -s ifconfig.me`  # == ip
+    IPCURL=$(curl -s https://ipinfo.io/$IP)
+    CITY_NAME=$(echo $IPCURL | jq -r ".city")
+    COUNTRY_CODE=$(echo $IPCURL | jq -r ".country")
+fi
+
+RESPONSE=""
+ERROR=0
+ERR_MSG=""
+if [ $UNITS = "kelvin" ]; then
+    UNIT_URL=""
+else
+    UNIT_URL="&units=$UNITS"
+fi
+URL="api.openweathermap.org/data/2.5/weather?appid=$APIKEY$UNIT_URL&lang=$LANG&q=$(echo $CITY_NAME| sed 's/ /%20/g'),${COUNTRY_CODE}"
+
+function getData {
+    ERROR=0
+    # For logging purposes
+    # echo " " >> "$HOME/.weather.log"
+    # echo `date`" ################################" >> "$HOME/.weather.log"
+    RESPONSE=`curl -s $URL`
+    CODE="$?"
+    if [ "$1" = "-d" ]; then
+        echo $RESPONSE
+        echo ""
+    fi
+    # echo "Response: $RESPONSE" >> "$HOME/.weather.log"
+    RESPONSECODE=0
+    if [ $CODE -eq 0 ]; then
+        RESPONSECODE=`echo $RESPONSE | jq .cod`
+    fi
+    if [ $CODE -ne 0 ] || [ ${RESPONSECODE:=429} -ne 200 ]; then
+        if [ $CODE -ne 0 ]; then
+            ERR_MSG="curl Error $CODE"
+            # echo "curl Error $CODE" >> "$HOME/.weather.log"
+        else
+            ERR_MSG="Conn. Err. $RESPONSECODE"
+            # echo "API Error $RESPONSECODE" >> "$HOME/.weather.log"
+        fi
+        ERROR=1
+    # else
+    #     echo "$RESPONSE" > "$HOME/.weather-last"
+    #     echo `date +%s` >> "$HOME/.weather-last"
+    fi
+}
+function setIcons {
+    if [ $WID -le 232 ]; then
+        #Thunderstorm
+        ICON_COLOR=$COLOR_THUNDER
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 311 ]; then
+        #Light drizzle
+        ICON_COLOR=$COLOR_LIGHT_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 321 ]; then
+        #Heavy drizzle
+        ICON_COLOR=$COLOR_HEAVY_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 531 ]; then
+        #Rain
+        ICON_COLOR=$COLOR_HEAVY_RAIN
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON=""
+        else
+            ICON=""
+        fi
+    elif [ $WID -le 622 ]; then
+        #Snow
+        ICON_COLOR=$COLOR_SNOW
+        ICON=""
+    elif [ $WID -le 771 ]; then
+        #Fog
+        ICON_COLOR=$COLOR_FOG
+        ICON=""
+    elif [ $WID -eq 781 ]; then
+        #Tornado
+        ICON_COLOR=$COLOR_TORNADO
+        ICON=""
+    elif [ $WID -eq 800 ]; then
+        #Clear sky
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON_COLOR=$COLOR_SUN
+            ICON=""
+        else
+            ICON_COLOR=$COLOR_MOON
+            ICON=""
+        fi
+    elif [ $WID -eq 801 ]; then
+        # Few clouds
+        if [ $DATE -ge $SUNRISE -a $DATE -le $SUNSET ]; then
+            ICON_COLOR=$COLOR_SUN
+            ICON=""
+        else
+            ICON_COLOR=$COLOR_MOON
+            ICON=""
+        fi
+    elif [ $WID -le 804 ]; then
+        # Overcast
+        ICON_COLOR=$COLOR_CLOUD
+        ICON=""
+    else
+        ICON_COLOR=$COLOR_ERR
+        ICON=""
+    fi
+    WIND=""
+    WINDFORCE=`echo "$RESPONSE" | jq .wind.speed`
+    WINDICON=""
+    if [ $BEAUFORTICON == "yes" ];then
+        WINDFORCE2=`echo "scale=$DECIMALS;$WINDFORCE * 3.6 / 1" | bc`
+        if [ $WINDFORCE2 -le 1 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 1 ] && [ $WINDFORCE2 -le 5 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 5 ] && [ $WINDFORCE2 -le 11 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 11 ] && [ $WINDFORCE2 -le 19 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 19 ] && [ $WINDFORCE2 -le 28 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 28 ] && [ $WINDFORCE2 -le 38 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 38 ] && [ $WINDFORCE2 -le 49 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 49 ] && [ $WINDFORCE2 -le 61 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 61 ] && [ $WINDFORCE2 -le 74 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 74 ] && [ $WINDFORCE2 -le 88 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 88 ] && [ $WINDFORCE2 -le 102 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 102 ] && [ $WINDFORCE2 -le 117 ]; then
+            WINDICON=""
+        elif [ $WINDFORCE2 -gt 117 ]; then
+            WINDICON=""
+        fi
+    fi
+    if [ $KNOTS = "yes" ]; then
+        case $UNITS in
+            "imperial") 
+                # The division by one is necessary because scale works only for divisions. bc is stupid.
+                WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 0.8689762419 / 1" | bc`
+                ;;
+            *)
+                WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 1.943844 / 1" | bc`
+                ;;
+        esac
+    else
+        if [ $UNITS != "imperial" ]; then
+            # Conversion from m/s to km/h
+            WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE * 3.6 / 1" | bc`
+        else
+            WINDFORCE=`echo "scale=$DECIMALS;$WINDFORCE / 1" | bc`
+        fi
+    fi
+    if [ "$DISPLAY_WIND" = "yes" ] && [ `echo "$WINDFORCE >= $MIN_WIND" |bc -l` -eq 1 ]; then
+        WIND="%{T$WEATHER_FONT_CODE}%{F$COLOR_WIND}$WINDICON%{F-}%{T-}"
+        if [ $DISPLAY_FORCE = "yes" ]; then
+            WIND="$WIND $COLOR_TEXT_BEGIN$WINDFORCE$COLOR_TEXT_END"
+            if [ $DISPLAY_WIND_UNIT = "yes" ]; then
+                if [ $KNOTS = "yes" ]; then
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}kn$COLOR_TEXT_END"
+                elif [ $UNITS = "imperial" ]; then
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}mph$COLOR_TEXT_END"
+                else
+                    WIND="$WIND ${COLOR_TEXT_BEGIN}km/h$COLOR_TEXT_END"
+                fi
+            fi
+        fi
+        WIND="$WIND |"
+    fi
+    if [ "$UNITS" = "metric" ]; then
+        TEMP_ICON="󰔄"
+    elif [ "$UNITS" = "imperial" ]; then
+        TEMP_ICON="󰔅"
+    else
+        TEMP_ICON="󰔆"
+    fi
+    
+    TEMP=`echo "$TEMP" | cut -d "." -f 1`
+    
+    if [ "$TEMP" -le $COLD_TEMP ]; then
+        TEMP="%{F$COLOR_COLD}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    elif [ `echo "$TEMP >= $HOT_TEMP" | bc` -eq 1 ]; then
+        TEMP="%{F$COLOR_HOT}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    else
+        TEMP="%{F$COLOR_NORMAL_TEMP}%{T$TEMP_FONT_CODE}%{T-}%{F-} $COLOR_TEXT_BEGIN$TEMP%{T$TEMP_FONT_CODE}$TEMP_ICON%{T-}$COLOR_TEXT_END"
+    fi
+}
+
+function outputCompact {
+    OUTPUT="$WIND %{T$WEATHER_FONT_CODE}%{F$ICON_COLOR}$ICON%{F-}%{T-} $ERR_MSG$COLOR_TEXT_BEGIN $DESCRIPTION$COLOR_TEXT_END| $TEMP"
+    # echo "Output: $OUTPUT" >> "$HOME/.weather.log"
+    echo "$OUTPUT"
+}
+
+getData $1
+if [ $ERROR -eq 0 ]; then
+    MAIN=`echo $RESPONSE | jq .weather[0].main`
+    WID=`echo $RESPONSE | jq .weather[0].id`
+    DESC=`echo $RESPONSE | jq .weather[0].description`
+    SUNRISE=`echo $RESPONSE | jq .sys.sunrise`
+    SUNSET=`echo $RESPONSE | jq .sys.sunset`
+    DATE=`date +%s`
+    WIND=""
+    TEMP=`echo $RESPONSE | jq .main.temp`
+    if [ $DISPLAY_LABEL = "yes" ]; then
+        DESCRIPTION=`echo "$RESPONSE" | jq .weather[0].description | tr -d '"' | awk '{for (i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1'`" "
+    else
+        DESCRIPTION=""
+    fi
+    PRESSURE=`echo $RESPONSE | jq .main.pressure`
+    HUMIDITY=`echo $RESPONSE | jq .main.humidity`
+    setIcons
+    outputCompact
+else
+    echo " "
+fi
diff --git a/polybar/modules/weather/openweathermap-simple.sh.old b/polybar/modules/weather/openweathermap-simple.sh.old
new file mode 100755 (executable)
index 0000000..8d2c477
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+get_icon() {
+    case $1 in
+        # Icons for weather-icons
+        01d) icon="";;
+        01n) icon="";;
+        02d) icon="";;
+        02n) icon="";;
+        03*) icon="󰖐";;
+        04d) icon="󰖕";;
+        04n) icon="󰼱";;
+        09*) icon="󰖗";;
+        10d) icon="";;
+        10n) icon="";;
+        11d) icon="";;
+        11n) icon="";;
+        13d) icon="";;
+        13n) icon="";;
+        50d) icon="";;
+        50n) icon="";;
+        *) icon="󰨹";
+    esac
+
+    echo $icon
+}
+
+KEY="aecd76a64e7f34dd2aae9249ca5d1304"
+CITY="3164527"
+UNITS="metric"
+SYMBOL="°"
+
+API="https://api.openweathermap.org/data/2.5"
+
+if [ -n "$CITY" ]; then
+    if [ "$CITY" -eq "$CITY" ] 2>/dev/null; then
+        CITY_PARAM="id=$CITY"
+    else
+        CITY_PARAM="q=$CITY"
+    fi
+
+    weather=$(curl -sf "$API/weather?appid=$KEY&$CITY_PARAM&units=$UNITS")
+else
+    location=$(curl -sf "https://location.services.mozilla.com/v1/geolocate?key=geoclue")
+
+    if [ -n "$location" ]; then
+        location_lat="$(echo "$location" | jq '.location.lat')"
+        location_lon="$(echo "$location" | jq '.location.lng')"
+
+        weather=$(curl -sf "$API/weather?appid=$KEY&lat=$location_lat&lon=$location_lon&units=$UNITS")
+    fi
+fi
+
+if [ -n "$weather" ]; then
+    weather_temp=$(echo "$weather" | jq ".main.temp" | cut -d "." -f 1)
+    weather_icon=$(echo "$weather" | jq -r ".weather[0].icon")
+
+    echo "$(get_icon "$weather_icon")" "$weather_temp$SYMBOL"
+fi
diff --git a/polybar/modules/windowlist.ini b/polybar/modules/windowlist.ini
new file mode 100644 (file)
index 0000000..a1a5f0c
--- /dev/null
@@ -0,0 +1,5 @@
+[module/windowlist]
+type = custom/script
+exec = ~/.config/polybar/scripts/windowlist/main 2> /dev/null
+tail = true
+
diff --git a/polybar/modules/windows.ini b/polybar/modules/windows.ini
new file mode 100644 (file)
index 0000000..c53f186
--- /dev/null
@@ -0,0 +1,11 @@
+[module/xwindow]
+type = internal/xwindow
+
+format = <label>
+format-foreground = ${colors.foreground}
+
+label = %title%
+label-maxlen = 30
+
+label-empty = "¯\_(ツ)_/¯"
+
diff --git a/polybar/modules/workspaces.ini b/polybar/modules/workspaces.ini
new file mode 100644 (file)
index 0000000..ff0687e
--- /dev/null
@@ -0,0 +1,37 @@
+[module/xworkspaces]
+type = internal/xworkspaces
+
+pin-workspaces = true
+enable-click = true
+enable-scroll = true
+
+label-active = %icon%
+#label-active-background = ${colors.background}
+label-active-foreground = ${colors.secondary}
+label-active-underline= ${colors.secondary}
+label-active-padding = 2
+
+label-occupied = %icon%
+#label-occupied-background = ${colors.background}
+label-occupied-padding = 2
+
+label-urgent = %icon%
+#label-urgent-background = ${colors.background}
+label-urgent-foreground = ${colors.alert}
+label-urgent-padding = 2
+
+label-empty = %icon%
+#label-empty-background = ${colors.background}
+label-empty-foreground = ${colors.foreground-alt}
+label-empty-padding = 2
+
+icon-0 = web;󰖟
+icon-1 = editor;
+icon-2 = console;󰆍
+icon-3 = ssh;󰢩
+icon-4 = graphic;󰽉
+icon-5 = editor2;󰼭
+icon-6 = chat;󰭻
+icon-7 = gaming;󰊴
+icon-default = 
+
diff --git a/polybar/scripts/windowlist/LICENSE b/polybar/scripts/windowlist/LICENSE
new file mode 100644 (file)
index 0000000..a9fce23
--- /dev/null
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2020 aroma1994
+Copyright (c) 2022 tuurep
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/polybar/scripts/windowlist/Makefile b/polybar/scripts/windowlist/Makefile
new file mode 100644 (file)
index 0000000..c5300da
--- /dev/null
@@ -0,0 +1,26 @@
+CFLAGS = -g -O2 -Wall
+LDFLAGS = -lX11
+
+all: main windowlist.o click-actions/raise click-actions/minimize click-actions/close
+
+main: main.c windowlist.o windowlist.h toml-c.h
+       gcc $(CFLAGS) $(LDFLAGS) -o main main.c windowlist.o
+
+windowlist.o: windowlist.c
+       gcc $(CFLAGS) -c windowlist.c
+
+click-actions/raise: click-actions/raise.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/raise click-actions/raise.c
+
+click-actions/minimize: click-actions/minimize.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/minimize click-actions/minimize.c
+
+click-actions/close: click-actions/close.c
+       gcc $(CFLAGS) $(LDFLAGS) -o click-actions/close click-actions/close.c
+
+clean:
+       rm windowlist.o
+       rm main
+       rm click-actions/raise
+       rm click-actions/minimize
+       rm click-actions/close
diff --git a/polybar/scripts/windowlist/README.md b/polybar/scripts/windowlist/README.md
new file mode 100644 (file)
index 0000000..2ecf707
--- /dev/null
@@ -0,0 +1,229 @@
+# windowlist
+
+![screenshot](screenshot.png)
+
+Began as a fork of my favorite Polybar script [polywins](https://github.com/uniquepointer/polywins)
+
+Windowlist has been fully rewritten in C using the relevant parts of the source code from [wmctrl](https://github.com/Conservatory/wmctrl) and [xprop](https://gitlab.freedesktop.org/xorg/app/xprop).
+
+## Improvements over polywins
+
+* Fixed a bug where names would not be correct if WM_CLASS contains spaces or dots
+* Option to sort the window list:
+    * By horizontal position on the screen
+    * By the application name
+* Ability to set nicknames for windows if a window has a bad default name
+* More flexible styling
+* Configurable click actions
+
+## Installation
+
+Project directory should be in `~/.config/polybar/scripts/`
+
+In `~/.config/polybar/scripts/windowlist/` run `make`
+
+Add module in `~/.config/polybar/config.ini`:
+
+```ini
+[module/windowlist]
+type = custom/script
+exec = ~/.config/polybar/scripts/windowlist/main 2> /dev/null
+tail = true
+```
+
+Add module `windowlist` in any of `modules-left`, `modules-center` or `modules-right`
+
+## Configuration
+
+Windowlist can be configured in `config.toml` in the root of the project.
+
+All options are detailed below:
+
+<table>
+    <tbody>
+        <tr>
+            <th>Option</th>
+            <th>Description</th>
+            <th>Possible values</th>
+        </tr>
+        <tr>
+            <td><code>sort_by</code></td>
+            <td>Criteria to sort the list of windows</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: unordered (WM client list order)</li>
+                    <li><code>"position"</code>: sort based on horizontal position on the screen</li>
+                    <li><code>"application"</code>: sort alphabetically based on the application class</li>
+                <ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>max_windows</code></td>
+            <td>How many windows can be visible on the list. Number of windows that did not fit will be shown e.g. <code>(+3)</code></td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>name</code></td>
+            <td>Which X window property is considered window name (label for a window)</td>
+            <td>
+                <ul>
+                    <li><code>"class"</code>: WM_CLASS</li>
+                    <li><code>"title"</code>: WM_NAME</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>name_case</code></td>
+            <td>Text case for window names</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: don't change capitalization</li>
+                    <li><code>"lowercase"</code>: all lowercase</li>
+                    <li><code>"uppercase"</code>: all uppercase</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>name_max_length</code></td>
+            <td>Maximum length for a window name before it's truncated with <code>‥</code></td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>name_padding</code></td>
+            <td>How many spaces to add before and after a window name</td>
+            <td align="center">number (int)</td>
+        </tr>
+        <tr>
+            <td><code>separator_string</code></td>
+            <td>String displayed between window names</td>
+            <td align="center">any string</td>
+        </tr>
+        <tr>
+            <td><code>empty_desktop_string</code></td>
+            <td>String to show when no windows are open</td>
+            <td align="center">any string</td>
+        </tr>
+        <tr>
+            <td>
+                <code>active_window_left_click</code><br>
+                <code>active_window_middle_click</code><br>
+                <code>active_window_right_click</code><br>
+                <code>active_window_scroll_up</code><br>
+                <code>active_window_scroll_down</code><br>
+                <code>inactive_window_left_click</code><br>
+                <code>inactive_window_middle_click</code><br>
+                <code>inactive_window_right_click</code><br>
+                <code>inactive_window_scroll_up</code><br>
+                <code>inactive_window_scroll_down</code><br>
+            </td>
+            <td>Click actions for window names can be set as <code>"raise"</code>, <code>"minimize"</code> or <code>"close"</code>, or a custom script/program in the <code>click-actions</code> directory. Window currently in focus (active) and unfocused windows (inactive) are configurable separately.</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no action</li>
+                    <li>name of script (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <code>active_window_fg_color</code>
+                <code>inactive_window_fg_color</code>
+                <code>separator_fg_color</code>
+                <code>empty_desktop_fg_color</code>
+                <code>overflow_fg_color</code>
+            </td>
+            <td>
+                Foreground colors for:
+                <ul>
+                    <li>Currently focused window</li>
+                    <li>Windows not in focus</li>
+                    <li>The <code>separator_string</code></li>
+                    <li>The <code>empty_desktop_string</code></li>
+                    <li>The string shown when <code>max_windows</code> exceeded</li>
+                </ul>
+            </td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: default polybar fg</code></li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>*_bg_color</code></td>
+            <td>All of the foreground colors have a background color counterpart, e.g. <code>active_window_bg_color</code></td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no background color</li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>*_ul_color</code></td>
+            <td>All colors also have an underline color counterpart, e.g. <code>active_window_ul_color</code><br><br>
+            Note that <code>line-size</code> must be set to 1 or higher in your polybar <code>config.ini</code>, otherwise underline isn't visible.</td>
+            <td>
+                <ul>
+                    <li><code>"none"</code>: no underline</li>
+                    <li>hex color (string)</li>
+                </ul>
+            </td>
+        </tr>
+        <tr>
+            <td><code>ignored_classes</code></td>
+            <td>Windows with a WM_CLASS in this array will not be shown on the bar. Strings are matched case insensitively.</td>
+            <td align="center">array of strings</td>
+        </tr>
+        <tr>
+            <td><code>window_nicknames</code></td>
+            <td>A window name can be substituted with a custom name using key value pairs. The keys are matched case insensitively.</td>
+            <td align="center">table of string key-value pairs</td>
+        </tr>
+    </tbody>
+</table>
+
+Note: polybar must be reset before changes take effect.
+
+### Defaults
+
+Check the `config.toml` in this repo, the options set there are the default values.
+
+You can also remove any key and it will fall back to the default value.
+
+### Scripting click actions
+
+The most convenient way is to write a shell script in the `click-actions` directory. Any language could be used, though. There are three "default" actions as small C programs: `raise`, `minimize` and `close`.
+
+You can write a new action as a script such as:
+
+`click-actions/foo.sh`
+
+```bash
+#!/bin/sh
+
+window_id="$1"
+
+# Do something with the window id of the window that has been clicked/scrolled on
+```
+
+Set the script as executable: `chmod +x click-actions/foo.sh`
+
+Then in `config.toml`:
+
+```toml
+active_window_middle_click = "foo.sh"
+```
+
+Window id is always given as arg `$1`. Tools I know that could be used to make something happen with a window id:
+
+* [wmctrl](https://github.com/Conservatory/wmctrl)
+* [wmutils](https://github.com/wmutils/core)
+* [xdo](https://github.com/baskerville/xdo)
+* [xdotool](https://github.com/jordansissel/xdotool)
+
+## Dependencies
+
+Requires an [EWMH](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html) compliant window manager
+
+* Wikipedia list of EWMH compliant window managers: [link](https://en.wikipedia.org/wiki/Extended_Window_Manager_Hints#List_of_window_managers_that_support_Extended_Window_Manager_Hints)
diff --git a/polybar/scripts/windowlist/click-actions/close b/polybar/scripts/windowlist/click-actions/close
new file mode 100755 (executable)
index 0000000..5220de1
Binary files /dev/null and b/polybar/scripts/windowlist/click-actions/close differ
diff --git a/polybar/scripts/windowlist/click-actions/close.c b/polybar/scripts/windowlist/click-actions/close.c
new file mode 100644 (file)
index 0000000..f44dd19
--- /dev/null
@@ -0,0 +1,46 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+int client_msg(Display* d, Window w, char* msg) {
+    XEvent e;
+    long mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+    e.xclient.type = ClientMessage;
+    e.xclient.serial = 0;
+    e.xclient.send_event = True;
+    e.xclient.message_type = XInternAtom(d, msg, False);
+    e.xclient.window = w;
+    e.xclient.format = 32;
+    e.xclient.data.l[0] = 0;
+    e.xclient.data.l[1] = 0;
+    e.xclient.data.l[2] = 0;
+    e.xclient.data.l[3] = 0;
+    e.xclient.data.l[4] = 0;
+
+    if (XSendEvent(d, DefaultRootWindow(d), False, mask, &e)) {
+        return EXIT_SUCCESS;
+    } else {
+        fprintf(stderr, "Cannot send %s event.\n", msg);
+        return EXIT_FAILURE;
+    }
+}
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    client_msg(d, wid, "_NET_CLOSE_WINDOW");
+    XCloseDisplay(d);
+}
diff --git a/polybar/scripts/windowlist/click-actions/minimize b/polybar/scripts/windowlist/click-actions/minimize
new file mode 100755 (executable)
index 0000000..10a671e
Binary files /dev/null and b/polybar/scripts/windowlist/click-actions/minimize differ
diff --git a/polybar/scripts/windowlist/click-actions/minimize.c b/polybar/scripts/windowlist/click-actions/minimize.c
new file mode 100644 (file)
index 0000000..010151d
--- /dev/null
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    XIconifyWindow(d, wid, DefaultScreen(d));
+    XCloseDisplay(d);
+}
diff --git a/polybar/scripts/windowlist/click-actions/raise b/polybar/scripts/windowlist/click-actions/raise
new file mode 100755 (executable)
index 0000000..44499ab
Binary files /dev/null and b/polybar/scripts/windowlist/click-actions/raise differ
diff --git a/polybar/scripts/windowlist/click-actions/raise.c b/polybar/scripts/windowlist/click-actions/raise.c
new file mode 100644 (file)
index 0000000..9818508
--- /dev/null
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+int client_msg(Display* d, Window w, char* msg) {
+    XEvent e;
+    long mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+    e.xclient.type = ClientMessage;
+    e.xclient.serial = 0;
+    e.xclient.send_event = True;
+    e.xclient.message_type = XInternAtom(d, msg, False);
+    e.xclient.window = w;
+    e.xclient.format = 32;
+    e.xclient.data.l[0] = 0;
+    e.xclient.data.l[1] = 0;
+    e.xclient.data.l[2] = 0;
+    e.xclient.data.l[3] = 0;
+    e.xclient.data.l[4] = 0;
+
+    if (XSendEvent(d, DefaultRootWindow(d), False, mask, &e)) {
+        return EXIT_SUCCESS;
+    } else {
+        fprintf(stderr, "Cannot send %s event.\n", msg);
+        return EXIT_FAILURE;
+    }
+}
+
+Window str_to_wid(char* str) {
+    unsigned long wid;
+    if (sscanf(str, "0x%lx", &wid) != 1) {
+        fputs("Cannot convert argument to number.\n", stderr);
+        return EXIT_FAILURE;
+    }
+    return (Window) wid;
+}
+
+int main(int argc, char* argv[]) {
+    // Must take a window id as first argument
+    Window wid = str_to_wid(argv[1]);
+
+    Display* d = XOpenDisplay(NULL);
+    client_msg(d, wid, "_NET_ACTIVE_WINDOW");
+    XMapRaised(d, wid);
+    XCloseDisplay(d);
+}
diff --git a/polybar/scripts/windowlist/config.toml b/polybar/scripts/windowlist/config.toml
new file mode 100644 (file)
index 0000000..ee49692
--- /dev/null
@@ -0,0 +1,52 @@
+sort_by = "none" # "none" | "position" | "application"
+max_windows = 13
+
+name = "class" # "class" | "title"
+name_case = "lowercase" # "none" | "lowercase" | "uppercase"
+name_max_length = 30
+name_padding = 1
+
+separator_string = "|"
+#empty_desktop_string = "¯\_(ツ)_/¯"
+empty_desktop_string = " lol "
+
+active_window_left_click   = "minimize"
+active_window_middle_click = "none"
+active_window_right_click  = "close"
+active_window_scroll_up    = "none"
+active_window_scroll_down  = "none"
+
+inactive_window_left_click   = "raise"
+inactive_window_middle_click = "none"
+inactive_window_right_click  = "close"
+inactive_window_scroll_up    = "none"
+inactive_window_scroll_down  = "none"
+
+active_window_fg_color = "none"
+active_window_bg_color = "none"
+active_window_ul_color = "none"
+
+inactive_window_fg_color = "#808080"
+inactive_window_bg_color = "none"
+inactive_window_ul_color = "none"
+
+separator_fg_color = "#808080"
+separator_bg_color = "none"
+separator_ul_color = "none"
+
+empty_desktop_fg_color = "none"
+empty_desktop_bg_color = "none"
+empty_desktop_ul_color = "none"
+
+overflow_fg_color = "none"
+overflow_bg_color = "none"
+overflow_ul_color = "none"
+
+ignored_classes = [
+    # "foo",
+    # "bar"
+]
+
+[window_nicknames]
+# foo = "bar"
+# baz = "qux"
diff --git a/polybar/scripts/windowlist/main b/polybar/scripts/windowlist/main
new file mode 100755 (executable)
index 0000000..c4e940d
Binary files /dev/null and b/polybar/scripts/windowlist/main differ
diff --git a/polybar/scripts/windowlist/main.c b/polybar/scripts/windowlist/main.c
new file mode 100644 (file)
index 0000000..a8f765a
--- /dev/null
@@ -0,0 +1,450 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <X11/Xlib.h>
+#include "toml-c.h"
+#include "windowlist.h"
+
+#define MAX_STR_LEN 200
+
+struct configuration {
+    char* sort_by;
+    int max_windows;
+
+    char* name;
+    char* name_case;
+    int name_max_length;
+    int name_padding;
+
+    char* empty_desktop_string;
+    char* separator_string;
+
+    char* active_window_left_click;
+    char* active_window_middle_click;
+    char* active_window_right_click;
+    char* active_window_scroll_up;
+    char* active_window_scroll_down;
+
+    char* inactive_window_left_click;
+    char* inactive_window_middle_click;
+    char* inactive_window_right_click;
+    char* inactive_window_scroll_up;
+    char* inactive_window_scroll_down;
+
+    char* active_window_fg_color;
+    char* active_window_bg_color;
+    char* active_window_ul_color;
+
+    char* inactive_window_fg_color;
+    char* inactive_window_bg_color;
+    char* inactive_window_ul_color;
+
+    char* separator_fg_color;
+    char* separator_bg_color;
+    char* separator_ul_color;
+
+    char* empty_desktop_fg_color;
+    char* empty_desktop_bg_color;
+    char* empty_desktop_ul_color;
+
+    char* overflow_fg_color;
+    char* overflow_bg_color;
+    char* overflow_ul_color;
+
+    toml_array_t* ignored_classes;
+    toml_table_t* window_nicknames;
+} config;
+
+toml_table_t* parse_config(char* filename, char* path) {
+    char config_path[MAX_STR_LEN];
+    snprintf(config_path, MAX_STR_LEN, "%s/%s", path, filename);
+
+    char errbuf[MAX_STR_LEN];
+
+    FILE* fp = fopen(config_path, "r");
+    toml_table_t* tbl = toml_parse_file(fp, errbuf, sizeof(errbuf));
+    fclose(fp);
+
+    toml_value_t opt;
+
+    opt = toml_table_string(tbl, "sort_by");  config.sort_by     = opt.ok ? opt.u.s : "none";
+    opt = toml_table_int(tbl, "max_windows"); config.max_windows = opt.ok ? opt.u.i : 13;
+
+    opt = toml_table_string(tbl, "name");         config.name            = opt.ok ? opt.u.s : "class";
+    opt = toml_table_string(tbl, "name_case");    config.name_case       = opt.ok ? opt.u.s : "lowercase";
+    opt = toml_table_int(tbl, "name_max_length"); config.name_max_length = opt.ok ? opt.u.i : 30;
+    opt = toml_table_int(tbl, "name_padding");    config.name_padding    = opt.ok ? opt.u.i : 1;
+
+    opt = toml_table_string(tbl, "empty_desktop_string"); config.empty_desktop_string = opt.ok ? opt.u.s : "";
+    opt = toml_table_string(tbl, "separator_string");     config.separator_string     = opt.ok ? opt.u.s : "·";
+
+    opt = toml_table_string(tbl, "active_window_left_click");   config.active_window_left_click   = opt.ok ? opt.u.s : "minimize";
+    opt = toml_table_string(tbl, "active_window_middle_click"); config.active_window_middle_click = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_right_click");  config.active_window_right_click  = opt.ok ? opt.u.s : "close";
+    opt = toml_table_string(tbl, "active_window_scroll_up");    config.active_window_scroll_up    = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_scroll_down");  config.active_window_scroll_down  = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "inactive_window_left_click");   config.inactive_window_left_click   = opt.ok ? opt.u.s : "raise";
+    opt = toml_table_string(tbl, "inactive_window_middle_click"); config.inactive_window_middle_click = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_right_click");  config.inactive_window_right_click  = opt.ok ? opt.u.s : "close";
+    opt = toml_table_string(tbl, "inactive_window_scroll_up");    config.inactive_window_scroll_up    = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_scroll_down");  config.inactive_window_scroll_down  = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "active_window_fg_color"); config.active_window_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_bg_color"); config.active_window_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "active_window_ul_color"); config.active_window_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "inactive_window_fg_color"); config.inactive_window_fg_color = opt.ok ? opt.u.s : "#808080";
+    opt = toml_table_string(tbl, "inactive_window_bg_color"); config.inactive_window_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "inactive_window_ul_color"); config.inactive_window_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "separator_fg_color"); config.separator_fg_color = opt.ok ? opt.u.s : "#808080";
+    opt = toml_table_string(tbl, "separator_bg_color"); config.separator_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "separator_ul_color"); config.separator_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "empty_desktop_fg_color"); config.empty_desktop_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "empty_desktop_bg_color"); config.empty_desktop_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "empty_desktop_ul_color"); config.empty_desktop_ul_color = opt.ok ? opt.u.s : "none";
+
+    opt = toml_table_string(tbl, "overflow_fg_color"); config.overflow_fg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "overflow_bg_color"); config.overflow_bg_color = opt.ok ? opt.u.s : "none";
+    opt = toml_table_string(tbl, "overflow_ul_color"); config.overflow_ul_color = opt.ok ? opt.u.s : "none";
+
+    // It's fine if these are NULL
+    config.ignored_classes  = toml_table_array(tbl, "ignored_classes");
+    config.window_nicknames = toml_table_table(tbl, "window_nicknames");
+
+    return tbl;
+}
+
+void lowercase(char* str) {
+    for(int i = 0; str[i]; i++) {
+        str[i] = tolower(str[i]);
+    }
+}
+
+void uppercase(char* str) {
+    for(int i = 0; str[i]; i++) {
+        str[i] = toupper(str[i]);
+    }
+}
+
+int compare_window_class(const void* v1, const void* v2) {
+    const struct window_props* p1 = v1;
+    const struct window_props* p2 = v2;
+    lowercase(p1->class);
+    lowercase(p2->class);
+    return strcmp(p1->class, p2->class);
+}
+
+int compare_position(const void* v1, const void* v2) {
+    // Sort wlist by horizontal position on screen
+    // If tied, vertical position decides (higher first)
+    const struct window_props* p1 = v1;
+    const struct window_props* p2 = v2;
+    if (p1->x < p2->x) return -1;
+    if (p1->x > p2->x) return 1;
+    if (p1->y < p2->y) return -1;
+    if (p1->y > p2->y) return 1;
+    return 0;
+}
+
+char* pad_spaces(char* window_name) {
+    int padding = config.name_padding;
+
+    int name_length = strlen(window_name);
+    int padded_length = name_length + (2 * padding);
+
+    char* padded_name = malloc(padded_length + 1);
+
+    memset(padded_name, ' ', padded_length);
+    memcpy(padded_name + padding, window_name, name_length);
+    padded_name[padded_length] = '\0';
+
+    return padded_name;
+}
+
+bool is_unused(char* option) {
+    if (option[0] == '\0' || !strcmp(option, "none")) {
+        return true;
+    }
+    return false;
+}
+
+bool is_ignored(char* class) {
+    if (!config.ignored_classes) {
+        return false;
+    }
+
+    for (int i = 0; i < toml_array_len(config.ignored_classes); i++) {
+        char* ignored_class = toml_array_string(config.ignored_classes, i).u.s;
+        if (!strcasecmp(class, ignored_class)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+char* get_window_nickname(char* class, char* title) {
+    if (!config.window_nicknames) {
+        return NULL;
+    }
+
+    for (int i = 0; i < toml_table_len(config.window_nicknames); i++) {
+        int keylen;
+
+        const char* key = toml_table_key(config.window_nicknames, i, &keylen);
+        char* val;
+
+        if (!strcmp(config.name, "title")) {
+            if (!strcasecmp(key, title)) {
+                val = toml_table_string(config.window_nicknames, key).u.s;
+                return val;
+            }
+        } else {
+            if (!strcasecmp(key, class)) {
+                val = toml_table_string(config.window_nicknames, key).u.s;
+                return val;
+            }
+        }
+    }
+    return NULL;
+}
+
+void print_polybar_str(char* label, char* fg_color, char* bg_color, char* ul_color,
+                       char* l_click, char* m_click, char* r_click, char* scroll_up, char* scroll_down) {
+
+    int actions_count = 0;
+
+    if (!is_unused(l_click)) {
+        printf("%%{A1:%s:}", l_click);
+        actions_count++;
+    }
+
+    if (!is_unused(m_click)) {
+        printf("%%{A2:%s:}", m_click);
+        actions_count++;
+    }
+
+    if (!is_unused(r_click)) {
+        printf("%%{A3:%s:}", r_click);
+        actions_count++;
+    }
+
+    if (!is_unused(scroll_up)) {
+        printf("%%{A4:%s:}", scroll_up);
+        actions_count++;
+    }
+
+    if (!is_unused(scroll_down)) {
+        printf("%%{A5:%s:}", scroll_down);
+        actions_count++;
+    }
+
+    if (!is_unused(bg_color)) {
+        printf("%%{B%s}", bg_color);
+    }
+
+    if (!is_unused(ul_color)) {
+        printf("%%{u%s}%%{+u}", ul_color);
+    }
+
+    if (!is_unused(fg_color)) {
+        printf("%%{F%s}", fg_color);
+    }
+
+    printf(label);
+
+    if (!is_unused(fg_color)) {
+        printf("%%{F-}");
+    }
+
+    if (!is_unused(ul_color)) {
+        printf("%%{-u}");
+    }
+
+    if (!is_unused(bg_color)) {
+        printf("%%{B-}");
+    }
+
+    for (int i = 0; i < actions_count; i++) {
+        printf("%%{A}");
+    }
+}
+
+void set_action_str(char* str, char* path, char* option, Window wid) {
+    if (is_unused(option)) {
+        strcpy(str, "none");
+        return;
+    }
+
+    snprintf(str, MAX_STR_LEN, "%s/click-actions/%s 0x%lx", path, option, wid);
+}
+
+void output(struct window_props* wlist, int n, Window active_window, char* path) {
+
+    if (!strcmp(config.sort_by, "application")) {
+        qsort(wlist, n, sizeof(struct window_props), compare_window_class);
+    }
+    if (!strcmp(config.sort_by, "position")) {
+        qsort(wlist, n, sizeof(struct window_props), compare_position);
+    }
+
+    int window_count = 0;
+
+    for (int i = 0; i < n; i++) {
+        if (is_ignored(wlist[i].class)) {
+            continue;
+        }
+
+        if (window_count > config.max_windows) {
+            window_count++;
+            continue;
+        }
+
+        if (window_count > 0) {
+            print_polybar_str(config.separator_string, config.separator_fg_color, config.separator_bg_color, config.separator_ul_color,
+                              "none", "none", "none", "none", "none");
+        }
+
+        char window_left_click  [MAX_STR_LEN];
+        char window_middle_click[MAX_STR_LEN];
+        char window_right_click [MAX_STR_LEN];
+        char window_scroll_up   [MAX_STR_LEN];
+        char window_scroll_down [MAX_STR_LEN];
+        char* window_fg_color;
+        char* window_bg_color;
+        char* window_ul_color;
+
+        if (wlist[i].id != active_window) {
+            set_action_str(window_left_click,   path, config.inactive_window_left_click,   wlist[i].id);
+            set_action_str(window_middle_click, path, config.inactive_window_middle_click, wlist[i].id);
+            set_action_str(window_right_click,  path, config.inactive_window_right_click,  wlist[i].id);
+            set_action_str(window_scroll_up,    path, config.inactive_window_scroll_up,    wlist[i].id);
+            set_action_str(window_scroll_down,  path, config.inactive_window_scroll_down,  wlist[i].id);
+            window_fg_color = config.inactive_window_fg_color;
+            window_bg_color = config.inactive_window_bg_color;
+            window_ul_color = config.inactive_window_ul_color;
+        } else {
+            set_action_str(window_left_click,   path, config.active_window_left_click,   wlist[i].id);
+            set_action_str(window_middle_click, path, config.active_window_middle_click, wlist[i].id);
+            set_action_str(window_right_click,  path, config.active_window_right_click,  wlist[i].id);
+            set_action_str(window_scroll_up,    path, config.active_window_scroll_up,    wlist[i].id);
+            set_action_str(window_scroll_down,  path, config.active_window_scroll_down,  wlist[i].id);
+            window_fg_color = config.active_window_fg_color;
+            window_bg_color = config.active_window_bg_color;
+            window_ul_color = config.active_window_ul_color;
+        }
+
+        char* window_nickname = get_window_nickname(wlist[i].class, wlist[i].title);
+        char* window_name = window_nickname;
+
+        if (!window_nickname) {
+            if (!strcmp(config.name, "title")) {
+                window_name = wlist[i].title;
+            } else {
+                window_name = wlist[i].class;
+            }
+        }
+
+        if (strlen(window_name) > config.name_max_length) {
+            // Name is truncated
+            strcpy(window_name + config.name_max_length, "‥");
+        }
+
+        if (!strcmp(config.name_case, "lowercase")) {
+            lowercase(window_name);
+        }
+        if (!strcmp(config.name_case, "uppercase")) {
+            uppercase(window_name);
+        }
+
+        char* padded_name = pad_spaces(window_name);
+
+        print_polybar_str(padded_name, window_fg_color, window_bg_color, window_ul_color,
+                          window_left_click, window_middle_click, window_right_click,
+                          window_scroll_up, window_scroll_down);
+
+        window_count++;
+        free(window_nickname);
+        free(padded_name);
+        free(wlist[i].class);
+        free(wlist[i].title);
+    }
+
+    if (window_count == 0) {
+        print_polybar_str(config.empty_desktop_string, config.empty_desktop_fg_color, config.empty_desktop_bg_color, config.empty_desktop_ul_color,
+                          "none", "none", "none", "none", "none");
+    }
+
+    if (window_count > config.max_windows) {
+        char overflow_string[20];
+        snprintf(overflow_string, 20, "(+%d)", window_count - config.max_windows);
+        print_polybar_str(overflow_string, config.overflow_fg_color, config.overflow_bg_color, config.overflow_ul_color,
+                          "none", "none", "none", "none", "none");
+    }
+
+    printf("\n");
+}
+
+void configure_windows_notify(Display* d, struct window_props* prev_wlist, int prev_wlist_len, struct window_props* wlist, int n) {
+    for (int i = 0; i < n; i++) {
+        bool found = false;
+        for (int j = 0; j < prev_wlist_len; j++) {
+            if (wlist[i].id == prev_wlist[j].id) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            XSelectInput(d, wlist[i].id, PropertyChangeMask);
+        }
+    }
+}
+
+int main(int argc, char* argv[]) {
+    Display* d = XOpenDisplay(NULL);
+
+    char* path = dirname(argv[0]);
+    toml_table_t* tbl = parse_config("config.toml", path);
+
+    // Ask X server to send ConfigureNotify and PropertyNotify events for root window
+    // ConfigureNotify is sent when a window's size or position changes
+    // PropertyNotify for changes in client list and active window
+    Window root = DefaultRootWindow(d);
+    XSelectInput(d, root, SubstructureNotifyMask | PropertyChangeMask);
+
+    XEvent e;
+    struct window_props* prev_wlist = NULL;
+    int prev_wlist_len = 0;
+
+    // Listen to XEvents forever and print the window list (output to stdout)
+    for (;;) {
+        fflush(stdout);
+        XNextEvent(d, &e);
+
+        long current_desktop_id = get_desktop_id(d, root, "_NET_CURRENT_DESKTOP");
+        Window active_window = get_active_window(d);
+
+        if (e.type == ConfigureNotify || e.type == PropertyNotify) {
+            int n;
+            struct window_props* wlist = generate_window_list(d, current_desktop_id, &n);
+
+            // Get events for individual windows' property changes,
+            // to know when a window's title (WM_NAME) changes
+            configure_windows_notify(d, prev_wlist, prev_wlist_len, wlist, n);
+
+            output(wlist, n, active_window, path);
+
+            free(prev_wlist);
+            prev_wlist = wlist;
+            prev_wlist_len = n;
+        }
+    }
+    free(prev_wlist);
+
+    toml_free(tbl);
+    XCloseDisplay(d);
+}
diff --git a/polybar/scripts/windowlist/screenshot.png b/polybar/scripts/windowlist/screenshot.png
new file mode 100644 (file)
index 0000000..10165cf
Binary files /dev/null and b/polybar/scripts/windowlist/screenshot.png differ
diff --git a/polybar/scripts/windowlist/toml-c.h b/polybar/scripts/windowlist/toml-c.h
new file mode 100644 (file)
index 0000000..a7365a6
--- /dev/null
@@ -0,0 +1,2132 @@
+// TOML config file parsing library
+// https://github.com/arp242/toml-c
+
+#ifndef TOML_H
+#define TOML_H
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#ifdef _MSC_VER
+#pragma warning(disable : 4996)
+#endif
+#ifdef __cplusplus
+#define TOML_EXTERN extern "C"
+#else
+#define TOML_EXTERN extern
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct toml_table_t     toml_table_t;
+typedef struct toml_array_t     toml_array_t;
+typedef struct toml_value_t     toml_value_t;
+typedef struct toml_timestamp_t toml_timestamp_t;
+typedef struct toml_keyval_t    toml_keyval_t;
+typedef struct toml_arritem_t   toml_arritem_t;
+
+// TOML table.
+struct toml_table_t {
+       const char *key;       // Key for this table
+       int keylen;            // length of key.
+       bool implicit;         // Table was created implicitly
+       bool readonly;         // No more modification allowed
+
+       int nkval;             // key-values in the table
+       toml_keyval_t **kval;
+       int narr;              // arrays in the table
+       toml_array_t **arr;
+       int ntab;              // tables in the table
+       toml_table_t **tab;
+};
+
+// TOML array.
+struct toml_array_t {
+       const char *key; // key to this array
+       int keylen;      // length of key.
+       int kind;        // element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed
+       int type;        // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed
+       int nitem;       // number of elements
+       toml_arritem_t *item;
+};
+struct toml_arritem_t {
+       int valtype; // for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp
+       char *val;
+       toml_array_t *arr;
+       toml_table_t *tab;
+};
+
+// TOML key/value pair.
+struct toml_keyval_t {
+       const char *key; // key to this value
+       int keylen;      // length of key.
+       const char *val; // the raw value
+};
+
+// Parsed TOML value.
+//
+// The string value s is a regular NULL-terminated C string, but the string
+// length is also given in sl since TOML values may contain NULL bytes. The
+// value is guaranteed to be correct UTF-8.
+struct toml_value_t {
+       bool ok; // Was this value present?
+       union {
+               toml_timestamp_t *ts; // datetime; must be freed after use.
+               char             *s;  // string value; must be freed after use
+               int              sl;  // string length, excluding NULL.
+               bool             b;   // bool value
+               int64_t          i;   // int value
+               double           d;   // double value
+       } u;
+};
+
+// Timestamp type; some values may be empty depending on the value of kind.
+struct toml_timestamp_t {
+       // 'd'atetime
+       // 'l'local-datetime
+       // 'D'ate-local
+       // 't'ime-local
+       char kind;
+       int year, month, day;
+       int hour, minute, second, millisec;
+       char *z;
+};
+
+// toml_parse() parses a TOML document from a string. Returns 0 on error, with
+// the error message stored in errbuf.
+//
+// toml_parse_file() is identical, but reads from a file descriptor.
+//
+// Use toml_free() to free the return value; this will invalidate all handles
+// for this table.
+       TOML_EXTERN toml_table_t *toml_parse      (char *toml, char *errbuf, int errbufsz);
+       TOML_EXTERN toml_table_t *toml_parse_file (FILE *fp, char *errbuf, int errbufsz);
+       TOML_EXTERN void          toml_free       (toml_table_t *table);
+
+// Table functions.
+//
+// toml_table_len() gets the number of direct keys for this table;
+// toml_table_key() gets the nth direct key in this table.
+       TOML_EXTERN int           toml_table_len       (const toml_table_t *table);
+       TOML_EXTERN const char   *toml_table_key       (const toml_table_t *table, int keyidx, int *keylen);
+       TOML_EXTERN toml_value_t  toml_table_string    (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_bool      (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_int       (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_double    (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_value_t  toml_table_timestamp (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_array_t *toml_table_array     (const toml_table_t *table, const char *key);
+       TOML_EXTERN toml_table_t *toml_table_table     (const toml_table_t *table, const char *key);
+
+// Array functions.
+       TOML_EXTERN int           toml_array_len       (const toml_array_t *array);
+       TOML_EXTERN toml_value_t  toml_array_string    (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_bool      (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_int       (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_double    (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_value_t  toml_array_timestamp (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_array_t *toml_array_array     (const toml_array_t *array, int idx);
+       TOML_EXTERN toml_table_t *toml_array_table     (const toml_array_t *array, int idx);
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define ALIGN8(sz) (((sz) + 7) & ~7)
+#define calloc(x, y) error - forbidden - use CALLOC instead
+static void *CALLOC(size_t nmemb, size_t sz) {
+       int nb = ALIGN8(sz) * nmemb;
+       void *p = malloc(nb);
+       if (p) {
+               memset(p, 0, nb);
+       }
+       return p;
+}
+
+// some old platforms define strdup macro -- drop it.
+#undef strdup
+#define strdup(x) error - forbidden - use STRDUP instead
+static char *STRDUP(const char *s) {
+       int len = strlen(s);
+       char *p = malloc(len + 1);
+       if (p) {
+               memcpy(p, s, len);
+               p[len] = 0;
+       }
+       return p;
+}
+
+// some old platforms define strndup macro -- drop it.
+#undef strndup
+#define strndup(x) error - forbiden - use STRNDUP instead
+static char *STRNDUP(const char *s, size_t n) {
+       size_t len = strnlen(s, n);
+       char *p = malloc(len + 1);
+       if (p) {
+               memcpy(p, s, len);
+               p[len] = 0;
+       }
+       return p;
+}
+
+// Unparsed values.
+typedef const char *toml_unparsed_t;
+toml_unparsed_t toml_table_unparsed  (const toml_table_t *table, const char *key);
+toml_unparsed_t toml_array_unparsed  (const toml_array_t *array, int idx);
+int             toml_value_string    (toml_unparsed_t s, char **ret, int *len);
+int             toml_value_bool      (toml_unparsed_t s, bool *ret);
+int             toml_value_int       (toml_unparsed_t s, int64_t *ret);
+int             toml_value_double    (toml_unparsed_t s, double *ret);
+int             toml_value_timestamp (toml_unparsed_t s, toml_timestamp_t *ret);
+
+// Convert escape to UTF-8; return #bytes used in buf to encode the char, or -1
+// on error.
+// http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16
+int read_unicode_escape(int64_t code, char buf[6]) {
+       if (0xd800 <= code && code <= 0xdfff) /// UTF-16 surrogates
+               return -1;
+       if (0x10FFFF < code)
+               return -1;
+       if (code < 0)
+               return -1;
+       if (code <= 0x7F) { /// 0x00000000 - 0x0000007F: 0xxxxxxx
+               buf[0] = (unsigned char)code;
+               return 1;
+       }
+       if (code <= 0x000007FF) { /// 0x00000080 - 0x000007FF: 110xxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xc0 | (code >> 6));
+               buf[1] = (unsigned char)(0x80 | (code & 0x3f));
+               return 2;
+       }
+       if (code <= 0x0000FFFF) { /// 0x00000800 - 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xe0 | (code >> 12));
+               buf[1] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | (code & 0x3f));
+               return 3;
+       }
+       if (code <= 0x001FFFFF) { /// 0x00010000 - 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xf0 | (code >> 18));
+               buf[1] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | (code & 0x3f));
+               return 4;
+       }
+       if (code <= 0x03FFFFFF) { /// 0x00200000 - 0x03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xf8 | (code >> 24));
+               buf[1] = (unsigned char)(0x80 | ((code >> 18) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[4] = (unsigned char)(0x80 | (code & 0x3f));
+               return 5;
+       }
+       if (code <= 0x7FFFFFFF) { /// 0x04000000 - 0x7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+               buf[0] = (unsigned char)(0xfc | (code >> 30));
+               buf[1] = (unsigned char)(0x80 | ((code >> 24) & 0x3f));
+               buf[2] = (unsigned char)(0x80 | ((code >> 18) & 0x3f));
+               buf[3] = (unsigned char)(0x80 | ((code >> 12) & 0x3f));
+               buf[4] = (unsigned char)(0x80 | ((code >> 6) & 0x3f));
+               buf[5] = (unsigned char)(0x80 | (code & 0x3f));
+               return 6;
+       }
+       return -1;
+}
+
+static inline void xfree(const void *x) {
+       if (x)
+               free((void *)(intptr_t)x);
+}
+
+enum tokentype_t {
+       INVALID,
+       DOT,
+       COMMA,
+       EQUAL,
+       LBRACE,
+       RBRACE,
+       NEWLINE,
+       LBRACKET,
+       RBRACKET,
+       STRING,
+};
+typedef enum tokentype_t tokentype_t;
+
+typedef struct token_t token_t;
+struct token_t {
+       tokentype_t tok;
+       int lineno;
+       char *ptr; // points into context->start
+       int len;
+       int eof;
+};
+
+typedef struct context_t context_t;
+struct context_t {
+       char *start;
+       char *stop;
+       char *errbuf;
+       int errbufsz;
+
+       token_t tok;
+       toml_table_t *root;
+       toml_table_t *curtab;
+
+       struct {
+               int top;
+               char *key[10];
+               int keylen[10];
+               token_t tok[10];
+       } tpath;
+};
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define FLINE __FILE__ ":" TOSTRING(__LINE__)
+
+static int next_token(context_t *ctx, bool dotisspecial);
+
+// Error reporting. Call when an error is detected. Always return -1.
+static int e_outofmemory(context_t *ctx, const char *fline) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline);
+       return -1;
+}
+
+static int e_internal(context_t *ctx, const char *fline) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline);
+       return -1;
+}
+
+static int e_syntax(context_t *ctx, int lineno, const char *msg) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+       return -1;
+}
+
+static int e_badkey(context_t *ctx, int lineno) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno);
+       return -1;
+}
+
+static int e_keyexists(context_t *ctx, int lineno) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno);
+       return -1;
+}
+
+static int e_forbid(context_t *ctx, int lineno, const char *msg) {
+       snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+       return -1;
+}
+
+static void *expand(void *p, int sz, int newsz) {
+       void *s = malloc(newsz);
+       if (!s)
+               return 0;
+
+       if (p) {
+               memcpy(s, p, sz);
+               free(p);
+       }
+       return s;
+}
+
+static void **expand_ptrarr(void **p, int n) {
+       void **s = malloc((n + 1) * sizeof(void *));
+       if (!s)
+               return 0;
+
+       s[n] = 0;
+       if (p) {
+               memcpy(s, p, n * sizeof(void *));
+               free(p);
+       }
+       return s;
+}
+
+static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) {
+       toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p));
+       if (!pp)
+               return 0;
+
+       memset(&pp[n], 0, sizeof(pp[n]));
+       return pp;
+}
+
+static uint8_t const u8_length[] = {1,1,1,1,1,1,1,1,0,0,0,0,2,2,3,4};
+#define u8length(s) u8_length[(((uint8_t *)(s))[0] & 0xFF) >> 4];
+
+static char *norm_lit_str(const char *src, int srclen, int *len, bool multiline, bool is_key, char *errbuf, int errbufsz) {
+       const char *sp  = src;
+       const char *sq  = src + srclen;
+       char       *dst = 0; /// will write to dst[] and return it
+       int        max  = 0; /// max size of dst[]
+       int        off  = 0; /// cur offset in dst[]
+
+       for (;;) { /// scan forward on src
+               if (off >= max - 10) { /// have some slack for misc stuff
+                       int newmax = max + 50;
+                       char *x = expand(dst, max, newmax);
+                       if (!x) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               return 0;
+                       }
+                       dst = x;
+                       max = newmax;
+               }
+
+               if (sp >= sq) /// finished?
+                       break;
+
+               uint8_t l = u8length(sp);
+               if (l == 0) {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                       return 0;
+               }
+               if (l > 1) {
+                       for (int i = 0; i < l; i++) {
+                               char ch = *sp++;
+                               if ((ch & 0x80) != 0x80) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                                       return 0;
+                               }
+                               dst[off++] = ch;
+                       }
+                       continue;
+               }
+
+               char ch = *sp++;
+               if (is_key && ch == '\n') {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "literal newlines not allowed in key");
+                       return 0;
+               }
+               /// control characters other than tab is not allowed
+               if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || ch == 0x7f) {
+                       if (!(multiline && (ch == '\r' || ch == '\n'))) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+                               return 0;
+                       }
+               }
+
+               dst[off++] = ch; /// a plain copy suffice
+       }
+
+       *len = off;
+       dst[off++] = 0;
+       return dst;
+}
+
+/* Convert src to raw unescaped utf-8 string.
+ * Returns NULL if error with errmsg in errbuf. */
+static char *norm_basic_str(const char *src, int srclen, int *len, bool multiline, bool is_key, char *errbuf, int errbufsz) {
+       const char *sp  = src;
+       const char *sq  = src + srclen;
+       char       *dst = 0; /// will write to dst[] and return it
+       int        max  = 0; /// max size of dst[]
+       int        off  = 0; /// cur offset in dst[]
+                                                ///
+       /// scan forward on src
+       for (;;) {
+               if (off >= max - 10) { /// have some slack for misc stuff
+                       int newmax = max + 50;
+                       char *x = expand(dst, max, newmax);
+                       if (!x) {
+                               xfree(dst);
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               return 0;
+                       }
+                       dst = x;
+                       max = newmax;
+               }
+
+               if (sp >= sq) /// finished?
+                       break;
+
+               uint8_t l = u8length(sp);
+               if (l == 0) {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                       return 0;
+               }
+               if (l > 1) {
+                       for (int i = 0; i < l; i++) {
+                               char ch = *sp++;
+                               if ((ch & 0x80) != 0x80) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid UTF-8 at byte pos %d", off);
+                                       return 0;
+                               }
+                               dst[off++] = ch;
+                       }
+                       continue;
+               }
+
+               char ch = *sp++;
+               if (is_key && ch == '\n') {
+                       xfree(dst);
+                       snprintf(errbuf, errbufsz, "literal newlines not allowed in key");
+                       return 0;
+               }
+               if (ch != '\\') {
+                       /// must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F
+                       if ((ch >= 0 && ch <= 0x08) || (ch >= 0x0a && ch <= 0x1f) || ch == 0x7f) {
+                               if (!(multiline && (ch == '\r' || ch == '\n'))) {
+                                       xfree(dst);
+                                       snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+                                       return 0;
+                               }
+                       }
+
+                       dst[off++] = ch; /// a plain copy suffice
+                       continue;
+               }
+
+               if (sp >= sq) { /// ch was backslash. we expect the escape char.
+                       snprintf(errbuf, errbufsz, "last backslash is invalid");
+                       xfree(dst);
+                       return 0;
+               }
+
+               if (multiline) { /// for multi-line, we want to kill line-ending-backslash.
+                       if (sp[strspn(sp, " \t\r")] == '\n') { /// if there is only whitespace after the backslash ...
+                               sp += strspn(sp, " \t\r\n"); /// skip all the following whitespaces
+                               continue;
+                       }
+               }
+
+               ch = *sp++; /// get the escaped char
+               switch (ch) {
+                       case 'u':
+                       case 'U': {
+                               int64_t ucs = 0;
+                               int nhex = (ch == 'u' ? 4 : 8);
+                               for (int i = 0; i < nhex; i++) {
+                                       if (sp >= sq) {
+                                               snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex);
+                                               xfree(dst);
+                                               return 0;
+                                       }
+                                       ch = *sp++;
+                                       int v = -1;
+                                       if ('0' <= ch && ch <= '9')
+                                               v = ch - '0';
+                                       else if ('A' <= ch && ch <= 'F')
+                                               v = ch - 'A' + 10;
+                                       else if ('a' <= ch && ch <= 'f')
+                                               v = (ch ^ 0x20) - 'A' + 10;
+                                       if (v == -1) {
+                                               snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U");
+                                               xfree(dst);
+                                               return 0;
+                                       }
+                                       ucs = ucs * 16 + v;
+                               }
+                               int n = read_unicode_escape(ucs, &dst[off]);
+                               if (n == -1) {
+                                       snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U");
+                                       xfree(dst);
+                                       return 0;
+                               }
+                               off += n;
+                       }; continue;
+                       case 'b':  ch = '\b';  break;
+                       case 't':  ch = '\t';  break;
+                       case 'n':  ch = '\n';  break;
+                       case 'f':  ch = '\f';  break;
+                       case 'r':  ch = '\r';  break;
+                       case '"':  ch = '"';   break;
+                       case '\\': ch = '\\'; break;
+                       default:
+                               snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch);
+                               xfree(dst);
+                               return 0;
+               }
+
+               dst[off++] = ch;
+       }
+
+       *len = off;
+       dst[off++] = 0; /// Cap with NUL and return it.
+       return dst;
+}
+
+// Normalize a key. Convert all special chars to raw unescaped utf-8 chars.
+static char *normalize_key(context_t *ctx, token_t strtok, int *keylen) {
+       const char *sp    = strtok.ptr;
+       const char *sq    = strtok.ptr + strtok.len;
+       int        lineno = strtok.lineno;
+       int        ch     = *sp;
+       char       *ret;
+
+       // Quoted string
+       if (ch == '\'' || ch == '\"') {
+               /// if ''' or """, take 3 chars off front and back. Else, take 1 char off.
+               bool multiline = (sp[1] == ch && sp[2] == ch);
+               if (multiline)
+                       sp += 3, sq -= 3;
+               else
+                       sp++, sq--;
+
+               char ebuf[80];
+               if (ch == '\'')
+                       ret = norm_lit_str(sp, sq - sp, keylen, multiline, true, ebuf, sizeof(ebuf));
+               else
+                       ret = norm_basic_str(sp, sq - sp, keylen, multiline, true, ebuf, sizeof(ebuf));
+               if (!ret) {
+                       e_syntax(ctx, lineno, ebuf);
+                       return 0;
+               }
+               return ret;
+       }
+
+       *keylen = 0;
+       for (const char *c = sp; c != sq; c++) { /// Bare key: allow: [A-Za-z0-9_-]+
+               *keylen = *keylen + 1;
+               if (isalnum(*c) || *c == '_' || *c == '-')
+                       continue;
+               e_badkey(ctx, lineno);
+               return 0;
+       }
+
+       if (!(ret = STRNDUP(sp, sq - sp))) { /// dup and return
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       return ret;
+}
+
+/* Look up key in tab. Return 0 if not found, or
+ * 'v'alue, 'a'rray or 't'able depending on the element. */
+static int check_key(toml_table_t *tab, const char *key, toml_keyval_t **ret_val, toml_array_t **ret_arr, toml_table_t **ret_tab) {
+       int i;
+       void *dummy;
+
+       if (!ret_tab)
+               ret_tab = (toml_table_t **)&dummy;
+       if (!ret_arr)
+               ret_arr = (toml_array_t **)&dummy;
+       if (!ret_val)
+               ret_val = (toml_keyval_t **)&dummy;
+
+       *ret_tab = 0;
+       *ret_arr = 0;
+       *ret_val = 0;
+
+       for (i = 0; i < tab->nkval; i++) {
+               if (strcmp(key, tab->kval[i]->key) == 0) {
+                       *ret_val = tab->kval[i];
+                       return 'v';
+               }
+       }
+       for (i = 0; i < tab->narr; i++) {
+               if (strcmp(key, tab->arr[i]->key) == 0) {
+                       *ret_arr = tab->arr[i];
+                       return 'a';
+               }
+       }
+       for (i = 0; i < tab->ntab; i++) {
+               if (strcmp(key, tab->tab[i]->key) == 0) {
+                       *ret_tab = tab->tab[i];
+                       return 't';
+               }
+       }
+       return 0;
+}
+
+static int key_kind(toml_table_t *tab, const char *key) {
+       return check_key(tab, key, 0, 0, 0);
+}
+
+/* Create a keyval in the table. */
+static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab, token_t keytok) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       toml_keyval_t *dest = 0;
+       if (key_kind(tab, newkey)) {
+               xfree(newkey);
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->nkval;
+       toml_keyval_t **base;
+       if ((base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->kval = base;
+
+       if ((base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+
+       dest = tab->kval[tab->nkval++];
+       dest->key = newkey;
+       dest->keylen = keylen;
+       return dest;
+}
+
+// Create a table in the table.
+static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab, token_t keytok) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       toml_table_t *dest = 0;
+       if (check_key(tab, newkey, 0, 0, &dest)) {
+               xfree(newkey);
+
+               /// Special case: make explicit if table exists and was created
+               /// implicitly.
+               if (dest && dest->implicit) {
+                       dest->implicit = false;
+                       return dest;
+               }
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->ntab;
+       toml_table_t **base;
+       if ((base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->tab = base;
+
+       if ((base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+
+       dest = tab->tab[tab->ntab++];
+       dest->key = newkey;
+       dest->keylen = keylen;
+       return dest;
+}
+
+// Create an array in the table.
+static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab, token_t keytok, char kind) {
+       int keylen;
+       char *newkey = normalize_key(ctx, keytok, &keylen);
+       if (!newkey)
+               return 0;
+
+       if (key_kind(tab, newkey)) {
+               xfree(newkey);
+               e_keyexists(ctx, keytok.lineno);
+               return 0;
+       }
+
+       int n = tab->narr;
+       toml_array_t **base;
+       if ((base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n)) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       tab->arr = base;
+
+       if ((base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n]))) == 0) {
+               xfree(newkey);
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_array_t *dest = tab->arr[tab->narr++];
+
+       dest->keylen = keylen;
+       dest->key = newkey;
+       dest->kind = kind;
+       return dest;
+}
+
+static toml_arritem_t *create_value_in_array(context_t *ctx, toml_array_t *parent) {
+       const int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       parent->item = base;
+       parent->nitem++;
+       return &parent->item[n];
+}
+
+/* Create an array in an array */
+static toml_array_t *create_array_in_array(context_t *ctx,
+               toml_array_t *parent) {
+       const int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t));
+       if (!ret) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       base[n].arr = ret;
+       parent->item = base;
+       parent->nitem++;
+       return ret;
+}
+
+/* Create a table in an array */
+static toml_table_t *create_table_in_array(context_t *ctx, toml_array_t *parent) {
+       int n = parent->nitem;
+       toml_arritem_t *base = expand_arritem(parent->item, n);
+       if (!base) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t));
+       if (!ret) {
+               e_outofmemory(ctx, FLINE);
+               return 0;
+       }
+       base[n].tab = ret;
+       parent->item = base;
+       parent->nitem++;
+       return ret;
+}
+
+static int skip_newlines(context_t *ctx, bool isdotspecial) {
+       while (ctx->tok.tok == NEWLINE) {
+               if (next_token(ctx, isdotspecial))
+                       return -1;
+               if (ctx->tok.eof)
+                       break;
+       }
+       return 0;
+}
+
+static int parse_keyval(context_t *ctx, toml_table_t *tab);
+
+static inline int eat_token(context_t *ctx, tokentype_t typ, bool isdotspecial, const char *fline) {
+       if (ctx->tok.tok != typ)
+               return e_internal(ctx, fline);
+
+       if (next_token(ctx, isdotspecial))
+               return -1;
+
+       return 0;
+}
+
+/* We are at '{ ... }'; parse the table. */
+static int parse_inline_table(context_t *ctx, toml_table_t *tab) {
+       if (eat_token(ctx, LBRACE, 1, FLINE))
+               return -1;
+
+       for (;;) {
+               if (ctx->tok.tok == NEWLINE)
+                       return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+               if (ctx->tok.tok == RBRACE) // until closing brace
+                       break;
+
+               if (ctx->tok.tok != STRING)
+                       return e_syntax(ctx, ctx->tok.lineno, "expect a string");
+
+               if (parse_keyval(ctx, tab))
+                       return -1;
+
+               if (ctx->tok.tok == NEWLINE)
+                       return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+               /* on comma, continue to scan for next keyval */
+               if (ctx->tok.tok == COMMA) {
+                       if (eat_token(ctx, COMMA, 1, FLINE))
+                               return -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (eat_token(ctx, RBRACE, 1, FLINE))
+               return -1;
+       tab->readonly = 1;
+       return 0;
+}
+
+static int valtype(const char *val) {
+       toml_timestamp_t ts;
+       if (*val == '\'' || *val == '"')
+               return 's';
+       if (toml_value_bool(val, false) == 0)
+               return 'b';
+       if (toml_value_int(val, 0) == 0)
+               return 'i';
+       if (toml_value_double(val, 0) == 0)
+               return 'd';
+       if (toml_value_timestamp(val, &ts) == 0) {
+               if (ts.year && ts.hour)
+                       return 'T'; /// timestamp
+               if (ts.year)
+                       return 'D'; /// date
+               return 't';   /// time
+       }
+       return 'u'; /// unknown
+}
+
+/* We are at '[...]' */
+static int parse_array(context_t *ctx, toml_array_t *arr) {
+       if (eat_token(ctx, LBRACKET, 0, FLINE))
+               return -1;
+
+       for (;;) {
+               if (skip_newlines(ctx, 0))
+                       return -1;
+
+               if (ctx->tok.tok == RBRACKET) /// until ]
+                       break;
+
+               switch (ctx->tok.tok) {
+                       case STRING: {
+                               /// set array kind if this will be the first entry
+                               if (arr->kind == 0)
+                                       arr->kind = 'v';
+                               else if (arr->kind != 'v')
+                                       arr->kind = 'm';
+
+                               char *val = ctx->tok.ptr;
+                               int vlen = ctx->tok.len;
+
+                               /// make a new value in array
+                               toml_arritem_t *newval = create_value_in_array(ctx, arr);
+                               if (!newval)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               if (!(newval->val = STRNDUP(val, vlen)))
+                                       return e_outofmemory(ctx, FLINE);
+
+                               newval->valtype = valtype(newval->val);
+
+                               /// set array type if this is the first entry
+                               if (arr->nitem == 1)
+                                       arr->type = newval->valtype;
+                               else if (arr->type != newval->valtype)
+                                       arr->type = 'm'; /// mixed
+
+                               if (eat_token(ctx, ctx->tok.tok, 0, FLINE))
+                                       return -1;
+                               break;
+                       }
+                       case LBRACKET: { /* [ [array], [array] ... ] */
+                               /* set the array kind if this will be the first entry */
+                               if (arr->kind == 0)
+                                       arr->kind = 'a';
+                               else if (arr->kind != 'a')
+                                       arr->kind = 'm';
+
+                               toml_array_t *subarr = create_array_in_array(ctx, arr);
+                               if (!subarr)
+                                       return -1;
+                               if (parse_array(ctx, subarr))
+                                       return -1;
+                               break;
+                       }
+                       case LBRACE: { /* [ {table}, {table} ... ] */
+                               /* set the array kind if this will be the first entry */
+                               if (arr->kind == 0)
+                                       arr->kind = 't';
+                               else if (arr->kind != 't')
+                                       arr->kind = 'm';
+
+                               toml_table_t *subtab = create_table_in_array(ctx, arr);
+                               if (!subtab)
+                                       return -1;
+                               if (parse_inline_table(ctx, subtab))
+                                       return -1;
+                               break;
+                       }
+                       default:
+                               return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+               }
+
+               if (skip_newlines(ctx, 0))
+                       return -1;
+
+               /* on comma, continue to scan for next element */
+               if (ctx->tok.tok == COMMA) {
+                       if (eat_token(ctx, COMMA, 0, FLINE))
+                               return -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (eat_token(ctx, RBRACKET, 1, FLINE))
+               return -1;
+       return 0;
+}
+
+/* handle lines like these:
+   key = "value"
+   key = [ array ]
+   key = { table } */
+static int parse_keyval(context_t *ctx, toml_table_t *tab) {
+       if (tab->readonly) {
+               return e_forbid(ctx, ctx->tok.lineno, "cannot insert new entry into existing table");
+       }
+
+       token_t key = ctx->tok;
+       if (eat_token(ctx, STRING, 1, FLINE))
+               return -1;
+
+       if (ctx->tok.tok == DOT) {
+               /* handle inline dotted key. e.g.
+                  physical.color = "orange"
+                  physical.shape = "round" */
+               toml_table_t *subtab = 0;
+               {
+                       int keylen;
+                       char *subtabstr = normalize_key(ctx, key, &keylen);
+                       if (!subtabstr)
+                               return -1;
+
+                       subtab = toml_table_table(tab, subtabstr);
+                       if (subtab)
+                               subtab->keylen = keylen;
+                       xfree(subtabstr);
+               }
+               if (!subtab) {
+                       subtab = create_keytable_in_table(ctx, tab, key);
+                       if (!subtab)
+                               return -1;
+               }
+               if (next_token(ctx, true))
+                       return -1;
+               if (parse_keyval(ctx, subtab))
+                       return -1;
+               return 0;
+       }
+
+       if (ctx->tok.tok != EQUAL)
+               return e_syntax(ctx, ctx->tok.lineno, "missing =");
+
+       if (next_token(ctx, false))
+               return -1;
+
+       switch (ctx->tok.tok) {
+               case STRING: { // key = "value"
+                       toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key);
+                       if (!keyval)
+                               return -1;
+                       token_t val = ctx->tok;
+
+                       assert(keyval->val == 0);
+                       if (!(keyval->val = STRNDUP(val.ptr, val.len)))
+                               return e_outofmemory(ctx, FLINE);
+
+                       if (next_token(ctx, true))
+                               return -1;
+
+                       return 0;
+               }
+               case LBRACKET: { /* key = [ array ] */
+                       toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0);
+                       if (!arr)
+                               return -1;
+                       if (parse_array(ctx, arr))
+                               return -1;
+                       return 0;
+               }
+               case LBRACE: { /* key = { table } */
+                       toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key);
+                       if (!nxttab)
+                               return -1;
+                       if (parse_inline_table(ctx, nxttab))
+                               return -1;
+                       return 0;
+               }
+               default:
+                       return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+       }
+       return 0;
+}
+
+typedef struct tabpath_t tabpath_t;
+struct tabpath_t {
+       int cnt;
+       token_t key[10];
+};
+
+/* at [x.y.z] or [[x.y.z]]
+ * Scan forward and fill tabpath until it enters ] or ]]
+ * There will be at least one entry on return. */
+static int fill_tabpath(context_t *ctx) {
+       // clear tpath
+       for (int i = 0; i < ctx->tpath.top; i++) {
+               char **p = &ctx->tpath.key[i];
+               xfree(*p);
+               *p = 0;
+       }
+       ctx->tpath.top = 0;
+
+       for (;;) {
+               if (ctx->tpath.top >= 10)
+                       return e_syntax(ctx, ctx->tok.lineno, "table path is too deep; max allowed is 10.");
+               if (ctx->tok.tok != STRING)
+                       return e_syntax(ctx, ctx->tok.lineno, "invalid or missing key");
+
+               int keylen;
+               char *key = normalize_key(ctx, ctx->tok, &keylen);
+               if (!key)
+                       return -1;
+               ctx->tpath.tok[ctx->tpath.top] = ctx->tok;
+               ctx->tpath.key[ctx->tpath.top] = key;
+               ctx->tpath.keylen[ctx->tpath.top] = keylen;
+               ctx->tpath.top++;
+
+               if (next_token(ctx, true))
+                       return -1;
+
+               if (ctx->tok.tok == RBRACKET)
+                       break;
+               if (ctx->tok.tok != DOT)
+                       return e_syntax(ctx, ctx->tok.lineno, "invalid key");
+               if (next_token(ctx, true))
+                       return -1;
+       }
+
+       if (ctx->tpath.top <= 0)
+               return e_syntax(ctx, ctx->tok.lineno, "empty table selector");
+       return 0;
+}
+
+/* Walk tabpath from the root, and create new tables on the way.
+ * Sets ctx->curtab to the final table. */
+static int walk_tabpath(context_t *ctx) {
+       toml_table_t *curtab = ctx->root; /// start from root
+
+       for (int i = 0; i < ctx->tpath.top; i++) {
+               const char *key = ctx->tpath.key[i];
+               int keylen = ctx->tpath.keylen[i];
+
+               toml_keyval_t *nextval = 0;
+               toml_array_t *nextarr = 0;
+               toml_table_t *nexttab = 0;
+               switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) {
+                       case 't': /// found a table. nexttab is where we will go next.
+                               break;
+                       case 'a': /// found an array. nexttab is the last table in the array.
+                               if (nextarr->kind != 't')
+                                       return e_internal(ctx, FLINE);
+
+                               if (nextarr->nitem == 0)
+                                       return e_internal(ctx, FLINE);
+
+                               nexttab = nextarr->item[nextarr->nitem - 1].tab;
+                               break;
+                       case 'v':
+                               return e_keyexists(ctx, ctx->tpath.tok[i].lineno);
+                       default: { /// Not found. Let's create an implicit table.
+                               int n = curtab->ntab;
+                               toml_table_t **base = (toml_table_t **)expand_ptrarr((void **)curtab->tab, n);
+                               if (base == 0)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               curtab->tab = base;
+
+                               if ((base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n]))) == 0)
+                                       return e_outofmemory(ctx, FLINE);
+
+                               if ((base[n]->key = STRDUP(key)) == 0)
+                                       return e_outofmemory(ctx, FLINE);
+                               base[n]->keylen = keylen;
+
+                               nexttab = curtab->tab[curtab->ntab++];
+
+                               /// tabs created by walk_tabpath are considered implicit
+                               nexttab->implicit = true;
+                       }; break;
+               }
+               curtab = nexttab; /// switch to next tab
+       }
+
+       ctx->curtab = curtab; /// save it
+       return 0;
+}
+
+/* handle lines like [x.y.z] or [[x.y.z]] */
+static int parse_select(context_t *ctx) {
+       assert(ctx->tok.tok == LBRACKET);
+
+       /* true if [[ */
+       int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '[');
+       /* need to detect '[[' on our own because next_token() will skip whitespace,
+          and '[ [' would be taken as '[[', which is wrong. */
+
+       /* eat [ or [[ */
+       if (eat_token(ctx, LBRACKET, 1, FLINE))
+               return -1;
+       if (llb) {
+               assert(ctx->tok.tok == LBRACKET);
+               if (eat_token(ctx, LBRACKET, 1, FLINE))
+                       return -1;
+       }
+
+       if (fill_tabpath(ctx))
+               return -1;
+
+       /* For [x.y.z] or [[x.y.z]], remove z from tpath. */
+       token_t z = ctx->tpath.tok[ctx->tpath.top - 1];
+       xfree(ctx->tpath.key[ctx->tpath.top - 1]);
+       ctx->tpath.top--;
+
+       /* set up ctx->curtab */
+       if (walk_tabpath(ctx))
+               return -1;
+
+       if (!llb) {
+               /* [x.y.z] -> create z = {} in x.y */
+               toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z);
+               if (!curtab)
+                       return -1;
+               ctx->curtab = curtab;
+       } else {
+               /* [[x.y.z]] -> create z = [] in x.y */
+               toml_array_t *arr = 0;
+               {
+                       int keylen;
+                       char *zstr = normalize_key(ctx, z, &keylen);
+                       if (!zstr)
+                               return -1;
+                       arr = toml_table_array(ctx->curtab, zstr);
+                       if (arr)
+                               arr->keylen = keylen;
+                       xfree(zstr);
+               }
+               if (!arr) {
+                       arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't');
+                       if (!arr)
+                               return -1;
+               }
+               if (arr->kind != 't')
+                       return e_syntax(ctx, z.lineno, "array mismatch");
+
+               /* add to z[] */
+               toml_table_t *dest;
+               {
+                       toml_table_t *t = create_table_in_array(ctx, arr);
+                       if (!t)
+                               return -1;
+
+                       if ((t->key = STRDUP("__anon__")) == 0)
+                               return e_outofmemory(ctx, FLINE);
+
+                       dest = t;
+               }
+
+               ctx->curtab = dest;
+       }
+
+       if (ctx->tok.tok != RBRACKET) {
+               return e_syntax(ctx, ctx->tok.lineno, "expects ]");
+       }
+       if (llb) {
+               if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) {
+                       return e_syntax(ctx, ctx->tok.lineno, "expects ]]");
+               }
+               if (eat_token(ctx, RBRACKET, 1, FLINE))
+                       return -1;
+       }
+
+       if (eat_token(ctx, RBRACKET, 1, FLINE))
+               return -1;
+       if (ctx->tok.tok != NEWLINE)
+               return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]");
+       return 0;
+}
+
+toml_table_t *toml_parse(char *toml, char *errbuf, int errbufsz) {
+       context_t ctx;
+
+       /// clear errbuf
+       if (errbufsz <= 0)
+               errbufsz = 0;
+       if (errbufsz > 0)
+               errbuf[0] = 0;
+
+       // init context
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.start = toml;
+       ctx.stop = ctx.start + strlen(toml);
+       ctx.errbuf = errbuf;
+       ctx.errbufsz = errbufsz;
+
+       // start with an artificial newline of length 0
+       ctx.tok.tok = NEWLINE;
+       ctx.tok.lineno = 1;
+       ctx.tok.ptr = toml;
+       ctx.tok.len = 0;
+
+       // make a root table
+       if ((ctx.root = CALLOC(1, sizeof(*ctx.root))) == 0) {
+               e_outofmemory(&ctx, FLINE);
+               return 0; // Do not goto fail, root table not set up yet
+       }
+
+       // set root as default table
+       ctx.curtab = ctx.root;
+
+       // Scan forward until EOF
+       for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) {
+               switch (tok.tok) {
+                       case NEWLINE:
+                               if (next_token(&ctx, true))
+                                       goto fail;
+                               break;
+
+                       case STRING:
+                               if (parse_keyval(&ctx, ctx.curtab))
+                                       goto fail;
+
+                               if (ctx.tok.tok != NEWLINE) {
+                                       e_syntax(&ctx, ctx.tok.lineno, "extra chars after value");
+                                       goto fail;
+                               }
+
+                               if (eat_token(&ctx, NEWLINE, 1, FLINE))
+                                       goto fail;
+                               break;
+
+                       case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */
+                               if (parse_select(&ctx))
+                                       goto fail;
+                               break;
+
+                       default:
+                               e_syntax(&ctx, tok.lineno, "syntax error");
+                               goto fail;
+               }
+       }
+
+       /// success
+       for (int i = 0; i < ctx.tpath.top; i++)
+               xfree(ctx.tpath.key[i]);
+       return ctx.root;
+
+fail:
+       // Something bad has happened. Free resources and return error.
+       for (int i = 0; i < ctx.tpath.top; i++)
+               xfree(ctx.tpath.key[i]);
+       toml_free(ctx.root);
+       return 0;
+}
+
+toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) {
+       int bufsz = 0;
+       char *buf = 0;
+       int off = 0;
+       int inc = 1024;
+
+       while (!feof(fp)) {
+               if (bufsz == 1024 * 20) /// Increment buffer by 20k after 20k.
+                       inc = 1024 * 20;
+               if (off == bufsz) {
+                       int xsz = bufsz + inc;
+                       char *x = expand(buf, bufsz, xsz);
+                       if (!x) {
+                               snprintf(errbuf, errbufsz, "out of memory");
+                               xfree(buf);
+                               return 0;
+                       }
+                       buf = x;
+                       bufsz = xsz;
+               }
+
+               errno = 0;
+               int n = fread(buf + off, 1, bufsz - off, fp);
+               if (ferror(fp)) {
+                       snprintf(errbuf, errbufsz, "%s", (errno ? strerror(errno) : "Error reading file"));
+                       xfree(buf);
+                       return 0;
+               }
+               off += n;
+       }
+
+       /// tag on a NUL to cap the string
+       if (off == bufsz) {
+               int xsz = bufsz + 1;
+               char *x = expand(buf, bufsz, xsz);
+               if (!x) {
+                       snprintf(errbuf, errbufsz, "out of memory");
+                       xfree(buf);
+                       return 0;
+               }
+               buf = x;
+               bufsz = xsz;
+       }
+       buf[off] = 0;
+
+       /// parse it, cleanup and finish.
+       toml_table_t *ret = toml_parse(buf, errbuf, errbufsz);
+       xfree(buf);
+       return ret;
+}
+
+static void xfree_kval(toml_keyval_t *p) {
+       if (!p)
+               return;
+       xfree(p->key);
+       xfree(p->val);
+       xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p);
+
+static void xfree_arr(toml_array_t *p) {
+       if (!p)
+               return;
+
+       xfree(p->key);
+       const int n = p->nitem;
+       for (int i = 0; i < n; i++) {
+               toml_arritem_t *a = &p->item[i];
+               if (a->val)
+                       xfree(a->val);
+               else if (a->arr)
+                       xfree_arr(a->arr);
+               else if (a->tab)
+                       xfree_tab(a->tab);
+       }
+       xfree(p->item);
+       xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p) {
+       if (!p)
+               return;
+
+       xfree(p->key);
+
+       for (int i = 0; i < p->nkval; i++)
+               xfree_kval(p->kval[i]);
+       xfree(p->kval);
+
+       for (int i = 0; i < p->narr; i++)
+               xfree_arr(p->arr[i]);
+       xfree(p->arr);
+
+       for (int i = 0; i < p->ntab; i++)
+               xfree_tab(p->tab[i]);
+       xfree(p->tab);
+
+       xfree(p);
+}
+
+void toml_free(toml_table_t *tab) { xfree_tab(tab); }
+
+static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr, int len) {
+       token_t t;
+       t.tok    = tok;
+       t.lineno = lineno;
+       t.ptr    = ptr;
+       t.len    = len;
+       t.eof    = 0;
+       ctx->tok = t;
+}
+
+static void set_eof(context_t *ctx, int lineno) {
+       set_token(ctx, NEWLINE, lineno, ctx->stop, 0);
+       ctx->tok.eof = 1;
+}
+
+/* Scan p for n digits compositing entirely of [0-9] */
+static int scan_digits(const char *p, int n) {
+       int ret = 0;
+       for (; n > 0 && isdigit(*p); n--, p++) {
+               ret = 10 * ret + (*p - '0');
+       }
+       return n ? -1 : ret;
+}
+
+static int scan_date(const char *p, int *YY, int *MM, int *DD) {
+       int year, month, day;
+       year = scan_digits(p, 4);
+       month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1;
+       day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1;
+       if (YY)
+               *YY = year;
+       if (MM)
+               *MM = month;
+       if (DD)
+               *DD = day;
+       return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1;
+}
+
+static int scan_time(const char *p, int *hh, int *mm, int *ss) {
+       int hour = scan_digits(p, 2);
+       int minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1;
+       int second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1;
+       if (hh)
+               *hh = hour;
+       if (mm)
+               *mm = minute;
+       if (ss)
+               *ss = second;
+       return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1;
+}
+
+static int scan_string(context_t *ctx, char *p, int lineno, bool dotisspecial) {
+       char *orig = p;
+
+       // Literal multiline.
+       if (strncmp(p, "'''", 3) == 0) {
+               char *q = p + 3;
+               while (true) {
+                       q = strstr(q, "'''");
+                       if (q == 0)
+                               return e_syntax(ctx, lineno, "unterminated triple-s-quote");
+                       int i = 0;
+                       while (q[3] == '\'') {
+                               i++;
+                               if (i >= 3)
+                                       return e_syntax(ctx, lineno, "too many ''' in triple-s-quote");
+                               q++;
+                       }
+                       break;
+               }
+               set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+               return 0;
+       }
+
+       // Multiline.
+       if (strncmp(p, "\"\"\"", 3) == 0) {
+               char *q = p + 3;
+               while (true) {
+                       q = strstr(q, "\"\"\"");
+                       if (q == 0)
+                               return e_syntax(ctx, lineno, "unterminated triple-d-quote");
+                       if (q[-1] == '\\') {
+                               q++;
+                               continue;
+                       }
+                       int i = 0;
+                       while (q[3] == '\"') {
+                               i++;
+                               if (i >= 3)
+                                       return e_syntax(ctx, lineno, "too many \"\"\" in triple-d-quote");
+                               q++;
+                       }
+                       break;
+               }
+
+               /// the string is [p+3, q-1]
+               int hexreq = 0; /// #hex required
+               bool escape = false;
+               for (p += 3; p < q; p++) {
+                       if (escape) {
+                               escape = false;
+                               if (strchr("btnfr\"\\", *p))
+                                       continue;
+                               if (*p == 'u') {
+                                       hexreq = 4;
+                                       continue;
+                               }
+                               if (*p == 'U') {
+                                       hexreq = 8;
+                                       continue;
+                               }
+                               if (p[strspn(p, " \t\r")] == '\n')
+                                       continue; /* allow for line ending backslash */
+                               return e_syntax(ctx, lineno, "bad escape char");
+                       }
+                       if (hexreq) {
+                               hexreq--;
+                               if (strchr("0123456789ABCDEFabcdef", *p))
+                                       continue;
+                               return e_syntax(ctx, lineno, "expect hex char");
+                       }
+                       if (*p == '\\') {
+                               escape = true;
+                               continue;
+                       }
+               }
+               if (escape)
+                       return e_syntax(ctx, lineno, "expect an escape char");
+               if (hexreq)
+                       return e_syntax(ctx, lineno, "expected more hex char");
+
+               set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+               return 0;
+       }
+
+       // Literal string.
+       if (*p == '\'') {
+               for (p++; *p && *p != '\n' && *p != '\''; p++)
+                       ;
+               if (*p != '\'')
+                       return e_syntax(ctx, lineno, "unterminated s-quote");
+
+               set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+               return 0;
+       }
+
+       // Basic String.
+       if (*p == '\"') {
+               int hexreq = 0; /// #hex required
+               bool escape = false;
+               for (p++; *p; p++) {
+                       if (escape) {
+                               escape = false;
+                               if (strchr("btnfr\"\\", *p))
+                                       continue;
+                               if (*p == 'u') {
+                                       hexreq = 4;
+                                       continue;
+                               }
+                               if (*p == 'U') {
+                                       hexreq = 8;
+                                       continue;
+                               }
+                               return e_syntax(ctx, lineno, "bad escape char");
+                       }
+                       if (hexreq) {
+                               hexreq--;
+                               if (strchr("0123456789ABCDEFabcdef", *p))
+                                       continue;
+                               return e_syntax(ctx, lineno, "expect hex char");
+                       }
+                       if (*p == '\\') {
+                               escape = true;
+                               continue;
+                       }
+                       if (*p == '\n')
+                               break;
+                       if (*p == '"')
+                               break;
+               }
+               if (*p != '"')
+                       return e_syntax(ctx, lineno, "unterminated quote");
+
+               set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+               return 0;
+       }
+
+       // Datetime.
+       if (scan_date(p, 0, 0, 0) == 0 || scan_time(p, 0, 0, 0) == 0) {
+               p += strspn(p, "0123456789.:+-Tt Zz"); /// forward thru the timestamp
+               for (; p[-1] == ' '; p--) /// squeeze out any spaces at end of string
+                       ;
+               set_token(ctx, STRING, lineno, orig, p - orig); /// tokenize
+               return 0;
+       }
+
+       // literals
+       for (; *p && *p != '\n'; p++) {
+               int ch = *p;
+               if (ch == '.' && dotisspecial)
+                       break;
+               if ('A' <= ch && ch <= 'Z')
+                       continue;
+               if ('a' <= ch && ch <= 'z')
+                       continue;
+               if (strchr("0123456789+-_.", ch))
+                       continue;
+               break;
+       }
+
+       set_token(ctx, STRING, lineno, orig, p - orig);
+       return 0;
+}
+
+static int next_token(context_t *ctx, bool dotisspecial) {
+       // Eat this tok.
+       char *p = ctx->tok.ptr;
+       int lineno = ctx->tok.lineno;
+       for (int i = 0; i < ctx->tok.len; i++)
+               if (*p++ == '\n')
+                       lineno++;
+
+       /// Make next tok
+       while (p < ctx->stop) {
+               if (*p == '#') { /// Skip comment. stop just before the \n.
+                       for (p++; p < ctx->stop && *p != '\n'; p++)
+                               ;
+                       continue;
+               }
+
+               if (dotisspecial && *p == '.') {
+                       set_token(ctx, DOT, lineno, p, 1);
+                       return 0;
+               }
+
+               switch (*p) {
+                       case ',':
+                               set_token(ctx, COMMA, lineno, p, 1);
+                               return 0;
+                       case '=':
+                               set_token(ctx, EQUAL, lineno, p, 1);
+                               return 0;
+                       case '{':
+                               set_token(ctx, LBRACE, lineno, p, 1);
+                               return 0;
+                       case '}':
+                               set_token(ctx, RBRACE, lineno, p, 1);
+                               return 0;
+                       case '[':
+                               set_token(ctx, LBRACKET, lineno, p, 1);
+                               return 0;
+                       case ']':
+                               set_token(ctx, RBRACKET, lineno, p, 1);
+                               return 0;
+                       case '\n':
+                               set_token(ctx, NEWLINE, lineno, p, 1);
+                               return 0;
+                       case '\r': case ' ': case '\t': /// ignore white spaces
+                               p++;
+                               continue;
+               }
+
+               return scan_string(ctx, p, lineno, dotisspecial);
+       }
+
+       set_eof(ctx, lineno);
+       return 0;
+}
+
+const char *toml_table_key(const toml_table_t *tab, int keyidx, int *keylen) {
+       if (keyidx < tab->nkval) {
+               *keylen = tab->kval[keyidx]->keylen;
+               return    tab->kval[keyidx]->key;
+       }
+       if ((keyidx -= tab->nkval) < tab->narr) {
+               *keylen = tab->arr[keyidx]->keylen;
+               return    tab->arr[keyidx]->key;
+       }
+       if ((keyidx -= tab->narr) < tab->ntab) {
+               *keylen = tab->tab[keyidx]->keylen;
+               return    tab->tab[keyidx]->key;
+       }
+       *keylen = 0;
+       return 0;
+}
+
+toml_unparsed_t toml_table_unparsed(const toml_table_t *tab, const char *key) {
+       for (int i = 0; i < tab->nkval; i++)
+               if (strcmp(key, tab->kval[i]->key) == 0)
+                       return tab->kval[i]->val;
+       return 0;
+}
+
+toml_array_t *toml_table_array(const toml_table_t *tab, const char *key) {
+       for (int i = 0; i < tab->narr; i++)
+               if (strcmp(key, tab->arr[i]->key) == 0)
+                       return tab->arr[i];
+       return 0;
+}
+
+toml_table_t *toml_table_table(const toml_table_t *tab, const char *key) {
+       for (int i  = 0; i < tab->ntab; i++)
+               if (strcmp(key, tab->tab[i]->key) == 0)
+                       return tab->tab[i];
+       return 0;
+}
+
+toml_unparsed_t toml_array_unparsed(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0;
+}
+
+int toml_table_len(const toml_table_t *tbl) {
+       return tbl->nkval + tbl->narr + tbl->ntab;
+}
+
+int toml_array_len(const toml_array_t *arr) {
+       return arr->nitem;
+}
+
+toml_array_t *toml_array_array(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0;
+}
+
+toml_table_t *toml_array_table(const toml_array_t *arr, int idx) {
+       return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0;
+}
+
+static int parse_millisec(const char *p, const char **endp);
+
+bool is_leap(int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); }
+
+int toml_value_timestamp(toml_unparsed_t src_, toml_timestamp_t *ret) {
+       if (!src_)
+               return -1;
+
+       const char *p = src_;
+       bool must_parse_time = false;
+
+       memset(ret, 0, sizeof(*ret));
+
+       /// YYYY-MM-DD
+       if (scan_date(p, &ret->year, &ret->month, &ret->day) == 0) {
+               if (ret->month < 1 || ret->day < 1 || ret->month > 12 || ret->day > 31)
+                       return -1;
+               if (ret->month == 2 && ret->day > (is_leap(ret->year) ? 29 : 28))
+                       return -1;
+               ret->kind = 'D';
+
+               p += 10;
+               if (*p) {
+                       if (*p != 'T' && *p != 't' && *p != ' ') /// T or space
+                               return -1;
+                       must_parse_time = true;
+                       p++;
+               }
+       }
+
+       /// HH:MM:SS
+       if (scan_time(p, &ret->hour, &ret->minute, &ret->second) == 0) {
+               if (ret->second < 0 || ret->minute < 0 || ret->hour < 0 || ret->hour > 23 || ret->minute > 59 || ret->second > 60)
+                       return -1;
+               ret->kind = (ret->kind == 'D' ? 'l' : 't');
+
+               p += 8;
+               if (*p == '.') { /// optionally, parse millisec
+                       p++; /// skip '.'
+                       const char *qq;
+                       ret->millisec = parse_millisec(p, &qq);
+                       p = qq;
+               }
+
+               if (*p) { /// parse and copy Z
+                       ret->kind = 'd';
+                       char *z = malloc(10);
+                       ret->z = z;
+                       if (*p == 'Z' || *p == 'z') {
+                               *z++ = 'Z';
+                               p++;
+                               *z = 0;
+                       } else if (*p == '+' || *p == '-') {
+                               *z++ = *p++;
+
+                               if (!(isdigit(p[0]) && isdigit(p[1])))
+                                       return -1;
+                               *z++ = *p++;
+                               *z++ = *p++;
+
+                               if (*p == ':') {
+                                       *z++ = *p++;
+                                       if (!(isdigit(p[0]) && isdigit(p[1])))
+                                               return -1;
+                                       *z++ = *p++;
+                                       *z++ = *p++;
+                               }
+
+                               *z = 0;
+                       }
+               }
+       }
+       if (*p != 0)
+               return -1;
+       if (must_parse_time && ret->kind == 'D')
+               return -1;
+       return 0;
+}
+
+/* Raw to boolean */
+int toml_value_bool(toml_unparsed_t src, bool *ret_) {
+       if (!src)
+               return -1;
+       bool dummy;
+       bool *ret = ret_ ? ret_ : &dummy;
+
+       if (strcmp(src, "true") == 0) {
+               *ret = true;
+               return 0;
+       }
+       if (strcmp(src, "false") == 0) {
+               *ret = false;
+               return 0;
+       }
+       return -1;
+}
+
+/* Raw to integer */
+int toml_value_int(toml_unparsed_t src, int64_t *ret_) {
+       if (!src)
+               return -1;
+
+       char buf[100];
+       char *p = buf;
+       char *q = p + sizeof(buf);
+       const char *s = src;
+       int base = 0;
+       int64_t dummy;
+       int64_t *ret = ret_ ? ret_ : &dummy;
+       bool have_sign = false;
+
+       if (s[0] == '+' || s[0] == '-') { /// allow +/-
+               have_sign = true;
+               *p++ = *s++;
+       }
+
+       if (s[0] == '_') /// disallow +_100
+               return -1;
+
+       if (s[0] == '0') { /// if 0* ...
+               switch (s[1]) {
+                       case 'x': base = 16; s += 2; break;
+                       case 'o': base = 8;  s += 2; break;
+                       case 'b': base = 2;  s += 2; break;
+                       case '\0':
+                               return *ret = 0, 0;
+                       default:
+                               if (s[1]) /// ensure no other digits after it
+                                       return -1;
+               }
+               if (!*s)
+                       return -1;
+               if (have_sign)   /// disallow +0xff, -0xff
+                       return -1;
+               if (s[0] == '_') /// disallow 0x_, 0o_, 0b_
+                       return -1;
+       }
+
+       while (*s && p < q) { /// just strip underscores and pass to strtoll
+               int ch = *s++;
+               if (ch == '_') {
+                       if (s[0] == '_')  /// disallow '__'
+                               return -1;
+                       if (s[0] == '\0') /// numbers cannot end with '_'
+                               return -1;
+                       continue; /// skip _
+               }
+               *p++ = ch;
+       }
+
+       if (*s || p == q) /// if not at end-of-string or we ran out of buffer ...
+               return -1;
+
+       *p = 0; /// cap with NUL
+
+       /// Run strtoll on buf to get the integer
+       char *endp;
+       errno = 0;
+       *ret = strtoll(buf, &endp, base);
+       return (errno || *endp) ? -1 : 0;
+}
+
+int toml_value_double(toml_unparsed_t src, double *ret_) {
+       if (!src)
+               return -1;
+
+       char buf[100];
+       char *p = buf;
+       char *q = p + sizeof(buf);
+       const char *s = src;
+       double dummy;
+       double *ret = ret_ ? ret_ : &dummy;
+       bool have_us = false;
+
+       if (s[0] == '+' || s[0] == '-') /// allow +/-
+               *p++ = *s++;
+
+       if (s[0] == '_') /// disallow +_1.00
+               return -1;
+
+       { /// decimal point, if used, must be surrounded by at least one digit on each side
+               char *dot = strchr(s, '.');
+               if (dot) {
+                       if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1]))
+                               return -1;
+               }
+       }
+
+       /// zero must be followed by . or 'e', or NUL
+       if (s[0] == '0' && s[1] && !strchr("eE.", s[1]))
+               return -1;
+
+       /// just strip underscores and pass to strtod
+       while (*s && p < q) {
+               int ch = *s++;
+               if (ch == '_') {
+                       have_us = true;
+                       if (s[0] == '_') /// disallow '__'
+                               return -1;
+                       if (s[0] == 'e') /// disallow _e
+                               return -1;
+                       if (s[0] == 0)   /// disallow last char '_'
+                               return -1;
+                       continue; /// skip _
+               }
+               if (ch == 'I' || ch == 'N' || ch == 'F' || ch == 'A') /// inf and nan are case-sensitive.
+                       return -1;
+               if (ch == 'e' && s[0] == '_') /// disallow e_
+                       return -1;
+               *p++ = ch;
+       }
+       if (*s || p == q)
+               return -1; /// reached end of string or buffer is full?
+
+       *p = 0; /// cap with NUL
+
+       /// Run strtod on buf to get the value
+       char *endp;
+       errno = 0;
+       *ret = strtod(buf, &endp);
+       if (errno || *endp)
+               return -1;
+       if (have_us && (isnan(*ret) || isinf(*ret)))
+               return -1;
+       return 0;
+}
+
+int toml_value_string(toml_unparsed_t src, char **ret, int *len) {
+       bool multiline = false;
+       const char *sp;
+       const char *sq;
+
+       *ret = 0;
+       if (!src)
+               return -1;
+
+       /// First char must be a s-quote or d-quote
+       int qchar = src[0];
+       int srclen = strlen(src);
+       if (!(qchar == '\'' || qchar == '"')) {
+               return -1;
+       }
+
+       /// triple quotes?
+       if (qchar == src[1] && qchar == src[2]) {
+               multiline = true;      /// triple-quote implies multiline
+               sp = src + 3;          /// first char after quote
+               sq = src + srclen - 3; /// first char of ending quote
+
+               if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar))
+                       return -1; /// last 3 chars in src must be qchar
+
+               if (sp[0] == '\n') /// skip new line immediate after qchar
+                       sp++;
+               else if (sp[0] == '\r' && sp[1] == '\n')
+                       sp += 2;
+       } else {
+               sp = src + 1;          /// first char after quote
+               sq = src + srclen - 1; /// ending quote
+               if (!(sp <= sq && *sq == qchar)) /// last char in src must be qchar
+                       return -1;
+       }
+
+       /// at this point:
+       ///     sp points to first valid char after quote.
+       ///     sq points to one char beyond last valid char.
+       ///     string len is (sq - sp).
+       if (qchar == '\'')
+               *ret = norm_lit_str(sp, sq - sp, len, multiline, false, 0, 0);
+       else
+               *ret = norm_basic_str(sp, sq - sp, len, multiline, false, 0, 0);
+       return *ret ? 0 : -1;
+}
+
+toml_value_t toml_array_string(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_string(toml_array_unparsed(arr, idx), &ret.u.s, &ret.u.sl) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_bool(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_bool(toml_array_unparsed(arr, idx), &ret.u.b) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_int(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_int(toml_array_unparsed(arr, idx), &ret.u.i) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_double(const toml_array_t *arr, int idx) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_double(toml_array_unparsed(arr, idx), &ret.u.d) == 0);
+       return ret;
+}
+
+toml_value_t toml_array_timestamp(const toml_array_t *arr, int idx) {
+       toml_timestamp_t ts;
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_timestamp(toml_array_unparsed(arr, idx), &ts) == 0);
+       if (ret.ok) {
+               ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+               if (ret.ok)
+                       *ret.u.ts = ts;
+       }
+       return ret;
+}
+
+toml_value_t toml_table_string(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       toml_unparsed_t raw = toml_table_unparsed(tbl, key);
+       if (raw)
+               ret.ok = (toml_value_string(raw, &ret.u.s, &ret.u.sl) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_bool(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_bool(toml_table_unparsed(tbl, key), &ret.u.b) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_int(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_int(toml_table_unparsed(tbl, key), &ret.u.i) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_double(const toml_table_t *tbl, const char *key) {
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_double(toml_table_unparsed(tbl, key), &ret.u.d) == 0);
+       return ret;
+}
+
+toml_value_t toml_table_timestamp(const toml_table_t *tbl, const char *key) {
+       toml_timestamp_t ts;
+       toml_value_t ret;
+       memset(&ret, 0, sizeof(ret));
+       ret.ok = (toml_value_timestamp(toml_table_unparsed(tbl, key), &ts) == 0);
+       if (ret.ok) {
+               ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+               if (ret.ok)
+                       *ret.u.ts = ts;
+       }
+       return ret;
+}
+
+static int parse_millisec(const char *p, const char **endp) {
+       int ret = 0;
+       int unit = 100; /// unit in millisec
+       for (; '0' <= *p && *p <= '9'; p++, unit /= 10)
+               ret += (*p - '0') * unit;
+       *endp = p;
+       return ret;
+}
+#endif // TOML_H
diff --git a/polybar/scripts/windowlist/windowlist.c b/polybar/scripts/windowlist/windowlist.c
new file mode 100644 (file)
index 0000000..da60813
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+    In this file is a stripped down version of wmctrl's source code
+
+    The original can be found at:
+        https://github.com/Conservatory/wmctrl
+
+    And a particularly helpful fork with some fixes and additions:
+        https://github.com/kfogel/wmctrl
+
+    Licensed under GPLv2
+
+    The function `list_windows` has been rewritten as `generate_window_list`
+    to get the properties in such a way that the list can be sorted based
+    on any criteria, and to be easy to format for the Polybar module.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xatom.h>
+#include "windowlist.h"
+
+#define MAX_PROPERTY_VALUE_LEN 4096
+
+char* get_property(Display* d, Window w, Atom xa_prop_type, char* prop_name,
+                   unsigned long* size) {
+    unsigned long ret_nitems, ret_bytes_after, tmp_size;
+    unsigned char* ret_prop;
+    int ret_format;
+    char* ret;
+
+    Atom xa_prop_name = XInternAtom(d, prop_name, False);
+    Atom xa_ret_type;
+
+    /*
+        MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
+
+        long_length = Specifies the length in 32-bit multiples of the
+                      data to be retrieved.
+
+        NOTE:  see
+        http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
+        In particular:
+
+        When the X window system was ported to 64-bit architectures, a
+        rather peculiar design decision was made. 32-bit quantities such
+        as Window IDs, atoms, etc, were kept as longs in the client side
+        APIs, even when long was changed to 64 bits.
+    */
+    if (XGetWindowProperty(d, w, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
+                           xa_prop_type, &xa_ret_type, &ret_format, &ret_nitems,
+                           &ret_bytes_after, &ret_prop) != Success) {
+        return NULL;
+    }
+
+    if (xa_ret_type != xa_prop_type) {
+        XFree(ret_prop);
+        return NULL;
+    }
+
+    // null terminate the result to make string handling easier
+
+    tmp_size = (ret_format / 8) * ret_nitems;
+    // Correct 64 Architecture implementation of 32 bit data
+    if(ret_format==32) tmp_size *= sizeof(long)/4;
+    ret = malloc(tmp_size + 1);
+    memcpy(ret, ret_prop, tmp_size);
+    ret[tmp_size] = '\0';
+
+    if (size) {
+        *size = tmp_size;
+    }
+
+    XFree(ret_prop);
+    return ret;
+}
+
+Window* get_client_list(Display* d, unsigned long* size) {
+    Window* client_list = NULL;
+    char* msg = NULL;
+
+    msg = "_NET_CLIENT_LIST or _WIN_CLIENT_LIST";
+    client_list = (Window*) get_property(d, DefaultRootWindow(d), XA_WINDOW,
+                                         "_NET_CLIENT_LIST", size);
+    if (!client_list)
+        client_list = (Window*) get_property(d, DefaultRootWindow(d), XA_CARDINAL,
+                                             "_WIN_CLIENT_LIST", size);
+    if (!client_list)
+        fprintf(stderr, "Cannot get client list properties.\n(%s)\n", msg);
+
+    return client_list;
+}
+
+Window get_active_window(Display* d) {
+    char* prop;
+    unsigned long size;
+    Window ret = (Window) 0;
+
+    prop = get_property(d, DefaultRootWindow(d), XA_WINDOW,
+                        "_NET_ACTIVE_WINDOW", &size);
+    if (prop) {
+        ret = *((Window*) prop);
+        free(prop);
+    }
+
+    return(ret);
+}
+
+long get_desktop_id(Display* d, Window w, char* prop_name) {
+    /*
+        Get current desktop ID:
+            w: DefaultRootWindow(d)
+            prop_name: _NET_CURRENT_DESKTOP
+
+        Get desktop ID for given window:
+            w: WID
+            prop_name: _NET_WM_DESKTOP
+    */
+    unsigned long* desktop = NULL;
+    long desktop_id;
+    if (! (desktop = (unsigned long*) get_property(d, w, XA_CARDINAL,
+                                                     prop_name, NULL))) {
+        if (! (desktop = (unsigned long*) get_property(d, w, XA_CARDINAL,
+                                                         "_WIN_WORKSPACE", NULL))) {
+            fprintf(stderr, "Getting desktop property failed (%s or _WIN_WORKSPACE)\n", prop_name);
+            free(desktop);
+            return -2; // Desktop -2 doesn't exist
+        }
+    }
+    desktop_id = (long) *desktop;
+    free(desktop);
+    return desktop_id;
+}
+
+void calculate_window_middle_x_y(Display* d, Window w, int* x, int* y) {
+    Window junkroot;
+    int junkx, junky;
+    unsigned int width, height, border_width, depth;
+
+    XGetGeometry(d, w, &junkroot, &junkx, &junky, &width, &height, &border_width, &depth);
+    XTranslateCoordinates(d, w, junkroot, junkx, junky, x, y, &junkroot);
+
+    *x = *x + width/2;
+    *y = *y + height/2;
+}
+
+char* get_window_class(Display* d, Window w) {
+    char* empty_wname = "N/A";
+    char* empty = malloc(strlen(empty_wname) + 1);
+    strcpy(empty, empty_wname);
+
+    unsigned long size;
+    char* wm_class = get_property(d, w, XA_STRING, "WM_CLASS", &size);
+
+    if (!wm_class) {
+        return empty;
+    }
+
+    char* class = calloc(size, sizeof(char));
+
+    /*
+       WM_CLASS contains two consecutive null-terminated strings:
+       <Instance>\0<Class>\0
+       We want the second one, so point after the first null-terminator.
+
+       More explanation on this pretty unintuitive window property:
+       https://unix.stackexchange.com/questions/494169/wm-class-vs-wm-instance
+    */
+    char* pointer_to_class = strchr(wm_class, '\0') + 1;
+    strcpy(class, pointer_to_class);
+
+    if (strlen(class) == 0) {
+        free(wm_class);
+        free(class);
+        return empty;
+    }
+
+    free(empty);
+    free(wm_class);
+    return class;
+}
+
+char* get_window_title(Display *d, Window w) {
+    char* empty_wname = "N/A";
+    char* empty = malloc(strlen(empty_wname) + 1);
+    strcpy(empty, empty_wname);
+
+    char* title_utf8;
+
+    char* wm_name = get_property(d, w, XA_STRING, "WM_NAME", NULL);
+    char* net_wm_name = get_property(d, w,
+            XInternAtom(d, "UTF8_STRING", False), "_NET_WM_NAME", NULL);
+
+    if (net_wm_name) {
+        title_utf8 = calloc(strlen(net_wm_name) + 1, sizeof(char));
+        strcpy(title_utf8, net_wm_name);
+    }
+    else {
+        if (wm_name) {
+            title_utf8 = calloc(strlen(wm_name) + 1, sizeof(char));
+            strcpy(title_utf8, wm_name);
+        }
+        else {
+            free(wm_name);
+            free(net_wm_name);
+            return empty;
+        }
+    }
+
+    free(empty);
+    free(wm_name);
+    free(net_wm_name);
+
+    return title_utf8;
+}
+
+int error_catcher(Display* d, XErrorEvent* e) {
+    /*
+       Ignore BadWindow error instead of halting program
+
+       Because XLib is async, a window may already be destroyed when I try to do
+       something with its ID.
+
+       See these:
+
+       https://stackoverflow.com/questions/44025639/how-can-i-check-in-xlib-if-window-exists
+       https://stackoverflow.com/questions/51908828/xlib-and-badwindow
+    */
+
+    if (e->error_code == BadWindow || e->error_code == BadDrawable) {
+        // get_desktop_id() causes BadDrawable by extension of ignoring BadWindow:
+        // gets an undefined window as argument
+        fprintf(stderr, "Expected XError type %d\n", e->error_code);
+        return EXIT_SUCCESS;
+    }
+    fprintf(stderr, "Unexpected XError type: %d\n", e->error_code);
+    return EXIT_FAILURE;
+}
+
+struct window_props* generate_window_list(Display* d, long current_desktop_id, int* window_list_size) {
+    Window* client_list;
+    unsigned long client_list_size;
+
+    if (! (client_list = get_client_list(d, &client_list_size))) {
+        return NULL;
+    }
+
+    int n_clients = client_list_size / sizeof(Window);
+    struct window_props* window_list = malloc(n_clients * sizeof(struct window_props));
+
+    // Number of windows on current desktop
+    int w_count = 0;
+
+    XSetErrorHandler(error_catcher);
+
+    // Populate the list
+    for (int i = 0; i < n_clients; i++) {
+        Window w = client_list[i];
+
+        long desktop_id = get_desktop_id(d, w, "_NET_WM_DESKTOP");
+        if (desktop_id != current_desktop_id) {
+            continue;
+        }
+
+        struct window_props wp;
+        wp.id = w;
+        wp.class = get_window_class(d, w);
+        wp.title = get_window_title(d, w);
+        calculate_window_middle_x_y(d, w, &wp.x, &wp.y);
+
+        window_list[w_count] = wp;
+        w_count++;
+    }
+
+    XSetErrorHandler(NULL);
+    free(client_list);
+
+    *window_list_size = w_count;
+
+    // Remove uninitialized part from array end
+    window_list = realloc(window_list, *window_list_size * sizeof(struct window_props));
+
+    return window_list;
+}
diff --git a/polybar/scripts/windowlist/windowlist.h b/polybar/scripts/windowlist/windowlist.h
new file mode 100644 (file)
index 0000000..402b3b7
--- /dev/null
@@ -0,0 +1,12 @@
+#include <X11/Xlib.h>
+
+struct window_props {
+    Window id;
+    char* class;
+    char* title;
+    int x, y;
+};
+
+struct window_props* generate_window_list(Display* d, long current_desktop_id, int* n_wprops);
+Window get_active_window(Display* d);
+long get_desktop_id(Display* d, Window w, char* prop_name);
diff --git a/polybar/scripts/windowlist/windowlist.o b/polybar/scripts/windowlist/windowlist.o
new file mode 100644 (file)
index 0000000..12aca5b
Binary files /dev/null and b/polybar/scripts/windowlist/windowlist.o differ