diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/lib/color b/lib/color new file mode 100644 index 0000000..4ed3a92 --- /dev/null +++ b/lib/color @@ -0,0 +1,36 @@ +#!/bin/bash + +## EMPTY DEFAULTS ## +color_bold="" +color_underline="" +color_standout="" +color_normal="" +color_black="" +color_red="" +color_green="" +color_yellow="" +color_blue="" +color_magenta="" +color_cyan="" +color_white="" + + +## ONLY IF PRINTING TO A TERMINAL ## +if test -t 1; then + # check count of supported colors + color_count=$(tput colors) + if [[ ! -z "$color_count" && "$color_count" -ge 8 ]]; then + color_bold="$(tput bold)" + color_underline="$(tput smul)" + color_standout="$(tput smso)" + color_normal="$(tput sgr0)" + color_black="$(tput setaf 0)" + color_red="$(tput setaf 1)" + color_green="$(tput setaf 2)" + color_yellow="$(tput setaf 3)" + color_blue="$(tput setaf 4)" + color_magenta="$(tput setaf 5)" + color_cyan="$(tput setaf 6)" + color_white="$(tput setaf 7)" + fi +fi diff --git a/lib/ini b/lib/ini new file mode 100644 index 0000000..7b72e88 --- /dev/null +++ b/lib/ini @@ -0,0 +1,34 @@ +#!/bin/bash + +ini_parse(){ + local filename="$1" + + sections="$(gawk '{ if ($1 ~ /^\[/) section=tolower(gensub(/\[(.+)\]/,"\\1",1,$1)); configuration[section]=1 } END {for (key in configuration) { print key} }' $filename)" + for section in $sections; do + array_name="ini_${section}" + declare -g -A ${array_name} + done + + eval $(gawk -F= '{ + if($1 ~ /^\[/) + section=tolower(gensub(/\[(.+)\]/,"\\1",1,$1)) + else if($1 !~ /^$/ && $1 !~ /^;/){ + gsub(/^[ \t]+|[ \t]+$/, "", $1); + gsub(/[\[\]]/, "", $1); + gsub(/^[ \t]+|[ \t]+$/, "", $2); + if(configuration[section][$1] == "") + configuration[section][$1]=$2 + else + configuration[section][$1]=configuration[section][$1]" "$2 + } + } + END { + for(section in configuration) + for(key in configuration[section]){ + section_name = section + gsub("-", "_", section_name) + print "ini_" section_name "[\""key"\"]=\""configuration[section][key]"\";" + } + }' ${filename} + ) +} diff --git a/lib/log b/lib/log new file mode 100644 index 0000000..4c2d597 --- /dev/null +++ b/lib/log @@ -0,0 +1,40 @@ +#!/bin/bash + +# +# Log a line with debug level. +# +log_debug(){ + echo -ne "${color_green}[DEBUG]${color_normal} " 1>&2 + echo "$1" 1>&2 +} + + + +# +# Log a line with info level. +# +log_info(){ + echo -ne "${color_blue}[INFO ]${color_normal} " 1>&2 + echo "$1" 1>&2 +} + + + +# +# Log a line with warning level. +# +log_warn(){ + echo -ne "${color_yellow}[WARN ]${color_normal} " 1>&2 + echo "$1" 1>&2 +} + + + +# +# Log a line with error level and exit. +# +log_error(){ + echo -ne "${color_red}[ERROR]${color_normal} " 1>&2 + echo "$1" 1>&2 + exit 1 +} diff --git a/lib/toolcheck b/lib/toolcheck new file mode 100644 index 0000000..fba19b2 --- /dev/null +++ b/lib/toolcheck @@ -0,0 +1,23 @@ +#!/bin/bash + +# +# Make sure all required tools are installed. +# +workplace_toolcheck(){ + ## BINARIES IN PATH ## + bin_tool_list=("grep xeventbind") + for b in $bin_tool_list; do + if [[ ! -f $(which "$b") ]]; then + log_error "Missing tool '$b'" + fi + done + + + ## DPKG PACKAGES ## + dpkg_tool_list=("gawk sed findutils procps usbutils libglib2.0-bin") + for d in $dpkg_tool_list; do + if ! dpkg -s $d 2>&1 | grep -q "Status: install ok installed"; then + log_error "Missing tool '$d'" + fi + done +} diff --git a/workplace b/workplace new file mode 100755 index 0000000..b123828 --- /dev/null +++ b/workplace @@ -0,0 +1,351 @@ +#!/bin/bash +set -euo pipefail + +## INIT ## +# get script base directory +SCRIPT_PATH=$(readlink -f "${BASH_SOURCE[0]}") +SCRIPT_DIR=$(dirname -- "$SCRIPT_PATH") + +# store arguments globally +ARG_LIST=("$@") + +# get version number +VERSION=$(cat ${SCRIPT_DIR}/VERSION | xargs) + +# location of workplace config file +CONFIG_PATH="${HOME}/.config/workplace.ini" + +# load libraries +source "${SCRIPT_DIR}/lib/color" +source "${SCRIPT_DIR}/lib/log" +source "${SCRIPT_DIR}/lib/toolcheck" +source "${SCRIPT_DIR}/lib/ini" + + + +# +# Explain usage of script. +# +workplace_help(){ + echo "WorkPlace v${VERSION}" + echo + echo "Usage: $0 " + echo + echo "Commands:" + echo " set - Set workplace" + echo " update - Automatically determine current workplace setup" + echo " updatedaemon - Run as daemon and call update when setup changes are detected" + echo + echo " help - Display this help message and exit" + echo " version - Display version information and exit" +} + + + +# +# Display version information. +# +workplace_version(){ + echo "WorkPlace v${VERSION}" + echo "(c) 2024 DrMaxNix" +} + + + +# +# Run commands `laptop` and `dock`. +# +workplace_set(){ + ## FIND CONFIG SECTION ## + # make sure param is present + if [[ ! ${1:-""} ]]; then + log_error "Missing workplace name; See '$0 help' for usage information" + fi + + # try to find section + workplace_section="" + for n in $(workplace_available_name_list); do + if [[ "$n" == "$1" ]]; then + workplace_name="$n" + fi + done + if [[ ! ${workplace_name:-""} ]]; then + log_error "Invalid workplace name '$1'; Check config file" + fi + + + ## RUN SET COMMANDS ## + local -n workplace_section_name="ini_$workplace_name" + for k in "${!workplace_section_name[@]}"; do + # make sure this is a `set` command + if [[ ! "$k" =~ ^set\. ]]; then + continue + fi + + # extract command and parameter + command=$(echo "$k" | grep -oPe '(?<=^set.).*') + param="${workplace_section_name[$k]}" + + # run command + workplace_setcommand_run "$command" "$param" + done +} + + + +# +# Run command `update`. +# +workplace_update(){ + ## DETECT SETUP ## + # connected usb devices + usb_id_list=$(workplace_usb_id_list) + + + ## FILTER ## + workplace_list_prio_0="" + workplace_list_prio_1="" + for n in $(workplace_available_name_list); do + local -n workplace_section_name="ini_$n" + + # default + trigger_default="${workplace_section_name[trigger.default]:-""}" + if [[ "$trigger_default" == "true" ]]; then + workplace_list_prio_0="${workplace_list_prio_0} $n" + fi + + # usb-id + trigger_usbid="${workplace_section_name[trigger.usb-id]:-""}" + if [[ "$trigger_usbid" ]]; then + for i in $usb_id_list; do + if [[ "$i" == "$trigger_usbid" ]]; then + workplace_list_prio_1="${workplace_list_prio_1} $n" + break + fi + done + fi + done + workplace_list_prio_0="$(echo $workplace_list_prio_0 | xargs)" + workplace_list_prio_1="$(echo $workplace_list_prio_1 | xargs)" + + + ## SELECT BEST ONE ## + workplace="" + + # by prio + if [[ "$workplace_list_prio_1" ]]; then + workplace="$workplace_list_prio_1" + elif [[ "$workplace_list_prio_0" ]]; then + workplace="$workplace_list_prio_0" + fi + + # make sure we have found one + if [[ ! "$workplace" ]]; then + log_warn "No matching workplace trigger found" + return + fi + + + ## SET NEW WORKPLACE ## + log_info "Switching to workplace '$workplace'" + workplace_set "$workplace" +} + + + +# +# Run command `updatedaemon`. +# +workplace_updatedaemon(){ + ## INIT ## + # prepare forking + trap "kill -s SIGTERM -- -$$" EXIT + + # update once + kill -s SIGUSR1 $$ + + + ## LISTEN FOR SCREEN RESOLUTION CHANGE ## + xeventbind resolution /usr/bin/echo | + while read -r; do + kill -s SIGUSR1 $$ + done & + + + ## LISTEN FOR LOGIN ## + gdbus monitor -y -d org.freedesktop.login1 | + grep --line-buffered "LockedHint" | + while read -r; do + kill -s SIGUSR1 $$ + done & + + + ## KEEP RUNNING ## + while true; do + workplace_handle_change + sleep 0.5s + done +} + + + +# +# HELPER: (Re-)load config. +# +workplace_config_load(){ + # clear used variables + for v in $(compgen -A variable | grep -Pe '^ini_'); do + unset $v + done + + # parse ini file + ini_parse "$CONFIG_PATH" +} + + + +# +# HELPER: Get defined workplace names. +# +workplace_available_name_list(){ + for v in $(compgen -A variable | grep -oPe '(?<=^ini_).*'); do + echo $v + done +} + + + +# +# HELPER: Run a `set` command. +# +workplace_setcommand_run(){ + case ${1:-""} in + ## CORE ## + # volume + "volume") + if [[ "$2" -le 0 ]]; then + amixer set 'Master',0 "$2%" off > /dev/null || true + else + amixer set 'Master',0 "$2%" on > /dev/null || true + fi + ;; + + # powerprofile + "powerprofile") + powerprofilesctl set "$2" || true + ;; + + # text scaling factor + "text-scaling-factor") + dconf write /org/gnome/desktop/interface/text-scaling-factor "$2" || true + ;; + + + ## DASH-TO-DOCK EXTENSION ## + # dock position (includes fix for buggy window size) + "dash-to-dock.position") + if [[ "$2" == "LEFT" ]]; then + dconf write /org/gnome/shell/extensions/dash-to-dock/dock-position "'BOTTOM'" || true + else + dconf write /org/gnome/shell/extensions/dash-to-dock/dock-position "'LEFT'" || true + fi + sleep 0.1s + dconf write /org/gnome/shell/extensions/dash-to-dock/dock-position "'$2'" || true + ;; + + + ## ERROR HANDLING ## + *) + log_error "Invalid setcommand '$1'" + exit 1 + ;; + esac +} + + + +# +# HELPER: Get list of connected usb device ids. +# +workplace_usb_id_list(){ + lsusb_id_regex='^Bus [0-9]+ Device [0-9]+: ID ([0-9a-f]{4}:[0-9a-f]{4}) .*$' + lsusb_out=$"$(lsusb)" + while IFS= read -r line; do + if [[ "$line" =~ $lsusb_id_regex ]]; then + echo $line | sed -E "s/$lsusb_id_regex/\1/g" + fi + done <<< "$lsusb_out" +} + + + +# +# HELPER: Handle detected setup change +# +trap "workplace_change_flag=1" SIGUSR1 +workplace_handle_change(){ + ## CHECK FLAG ## + # ignore if not set + if [[ ! ${workplace_change_flag:-""} -gt 0 ]]; then + return + fi + + # unset flag + workplace_change_flag=0 + + + ## DO UPDATE ## + # reload config + workplace_config_load + + # run update function + workplace_update + + # sleep to prevent spamming + sleep 1s +} + + + +## MAIN ## +case ${1:-""} in + set) + workplace_toolcheck + workplace_config_load + workplace_set "$2" + exit 0 + ;; + + update) + workplace_toolcheck + workplace_config_load + workplace_update + exit 0 + ;; + + updatedaemon) + workplace_toolcheck + workplace_updatedaemon + exit 0 + ;; + + help) + workplace_help + exit 0 + ;; + + version) + workplace_version + exit 0 + ;; + + "") + log_error "No command given; See '$0 help' for usage information" + exit 1 + ;; + + *) + log_error "Invalid command '$1'; See '$0 help' for usage information" + exit 1 + ;; +esac diff --git a/workplace.desktop b/workplace.desktop new file mode 100644 index 0000000..e3c26e9 --- /dev/null +++ b/workplace.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=WorkPlace Update Daemon +Type=Application +Exec=~/.local/bin/workplace updatedaemon +Terminal=false diff --git a/workplace.template.ini b/workplace.template.ini new file mode 100644 index 0000000..a91a815 --- /dev/null +++ b/workplace.template.ini @@ -0,0 +1,17 @@ +[laptop] +trigger.default = true + +set.dash-to-dock.position = BOTTOM +set.text-scaling-factor = 1.3700000000000001 +set.powerprofile = power-saver +set.volume = 0 + + + +[dock] +trigger.usb-id = 1234:5678 + +set.dash-to-dock.position = LEFT +set.text-scaling-factor = 1.0 +set.powerprofile = balanced +set.volume = 90