diff --git a/firestarter b/firestarter index 50d5e0b..0c679bd 100755 --- a/firestarter +++ b/firestarter @@ -6,60 +6,76 @@ # # Distributed under terms of the MIT license. # +set -e -# "Globals" -_name="firestarter" -_configdir="${XDG_CONFIG_HOME:-$HOME/.config}/$_name" -_logdir="${XDG_DATA_HOME:-$HOME/.local/share}/$_name/logs" -_firestarterrc="$HOME/.firestarterrc" -_sessionid="$(< /proc/self/sessionid)" -# Dummy fd for read sleeping -exec 1023<> <(:) +# Read-only set-once variables +declare -r _name="$(basename -- "$0")" +declare -r _sessionid="$(< /proc/self/sessionid)" +# Options +declare _optconfigdir="${XDG_CONFIG_HOME:-$HOME/.config}/${_name}" +declare _optdryrun +declare _optlogdir="$_optconfigdir/logs" +declare _optpregen +declare -i _opthelp +declare -i _optverbose +# Working variables +declare -a _args +declare _return -# Basic functions -print() { - # Write a message to STDOUT without a name attached - # 1: That message - [ -z "$1" ] && return 1 - printf "%s\\n" \ - "$1" -} +# Helper functions log() { - # Write a message to STDOUT - # 1: That message + # Print a line to the terminal if _optverbose is greater than $2 + # $2 defaults to 0 + # loglevel 0: Daily-use messages + # loglevel 1: Detailed but not quite debugging + # loglevel 2: Definitely debugging [ -z "$1" ] && return 1 - printf "%s log: %s\\n" \ - "$_name" \ - "$1" >&1 -} -err() { - # Write a message to STDERR, also exit if arg 2 is specified - # 1: That message - # 2: An optional exit code (will only exit if provided) - [ -z "$1" ] && return 1 - printf "%s err: %s\\n" \ - "$_name" \ - "$1" >&2 - [ -z "$2" ] && return 0 - if ! [ "$2" -ge "0" ] > /dev/null 2>&1; then - err "Attempted to exit with malformed exit code \"$2\"" 10 - else - exit "$2" + if (( _optverbose >= ${2:-0} )); then + printf "%s\\n" "$1" fi } -has() { - # See if a program exists in $PATH - # 1: A program +warn() { + # Print a yellow line to the terminal, respecting _optverbose [ -z "$1" ] && return 1 - command -v "$1" > /dev/null 2>&1 + if (( _optverbose >= ${2:-0} )); then + if [ -t 1 ]; then + printf "\\e[33m%s\\e[0m\\n" "$1" + else + printf "WARN: %s\\n" "$1" + fi + fi } -gettarget() { - # Parse a defaults file to get the target program - # 1: A defaults file +error() { + # Print a red line to the terminal, exit if $2 is specified [ -z "$1" ] && return 1 + if [ -t 2 ]; then + printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2 + else + printf "ERROR: %s\\n" "$1" 1>&2 + fi + [ -z "$2" ] && return + exit "${2:-1}" +} +has() { + # Parse out all arguments and try to find them in path + # If an argument cannot be found, set _return and fail + for prog in "$@"; do + if ! command -v "$prog" > /dev/null 2>&1; then + _return="$prog" + return 1 + fi + done + return 0 +} + +# Core program functions +gettarget() { + # Parse a defaults file to get a target program + [ -z "$1" ] && return 1 + [ -d "$1" ] && return 1 [ -r "$1" ] || return 1 - # Every odd line is the check line - # Every even one is the exec line + # Every odd line is a condition + # Every even one is a target local firstline while read -r checkline; do if [ -z "$firstline" ]; then @@ -70,9 +86,7 @@ gettarget() { return 50 fi fi - if [ "${checkline#"#"}" != "$checkline" ]; then - continue - fi + [ "${checkline#"#"}" != "$checkline" ] && continue read -r execline if bash -c "$checkline" > /dev/null 2>&1; then _return="$execline" @@ -83,19 +97,17 @@ gettarget() { done < "$1" return 2 } - -# Steps in execution -step_generate() { - log "Creating default config setup in \"$_configdir\"" +genconfigs() { + log "Creating default config setup in \"$_optconfigdir\"" log "See firestarter -h for more information" # Audio daemon - cat << EOF > "$_configdir/audio-daemon" + cat << EOF > "$_optconfigdir/audio-daemon" #.fsdefaults command -v pulseaudio pulseaudio EOF # Information bars - cat << EOF > "$_configdir/bar" + cat << EOF > "$_optconfigdir/bar" #.fsdefaults command -v polybar && [ -r "$HOME/.config/polybar/launch.sh" ] "$HOME/.config/polybar/launch.sh" @@ -111,7 +123,7 @@ command -v xfce4-panel xfce4-panel EOF # Blue light filter - cat << EOF > "$_configdir/blue-light-filter" + cat << EOF > "$_optconfigdir/blue-light-filter" #.fsdefaults command -v redshift-gtk redshift-gtk @@ -119,7 +131,7 @@ command -v redshift redshift EOF # Compositor - cat << EOF > "$_configdir/compositor" + cat << EOF > "$_optconfigdir/compositor" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -131,7 +143,7 @@ command -v xcompmgr xcompmgr EOF # Polkit authentication agents - cat << EOF > "$_configdir/polkit-agent" + cat << EOF > "$_optconfigdir/polkit-agent" #.fsdefaults command -v lxqt-policykit-agent lxqt-policykit-agent @@ -176,7 +188,7 @@ polkit-efl-authentication-agent-1 /usr/libexec/polkit-gnome-authentication-agent-1 EOF # Hotkey daemon - cat << EOF > "$_configdir/hotkey-daemon" + cat << EOF > "$_optconfigdir/hotkey-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -186,13 +198,13 @@ command -v lxqt-globalkeysd lxqt-globalkeysd EOF # Network daemon - cat << EOF > "$_configdir/network-daemon" + cat << EOF > "$_optconfigdir/network-daemon" #.fsdefaults command -v nm-applet nm-applet EOF # Notification daemon - cat << EOF > "$_configdir/notification-daemon" + cat << EOF > "$_optconfigdir/notification-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -202,7 +214,7 @@ command -v lxqt-notificationd notificationd EOF # Power daemons - cat << EOF > "$_configdir/power-daemon" + cat << EOF > "$_optconfigdir/power-daemon" #.fsdefaults command -v batterymon batterymon @@ -219,13 +231,13 @@ gnome-power-manager EOF # Runners # Note that rofi is not a daemon and is not included here - cat << EOF > "$_configdir/runner" + cat << EOF > "$_optconfigdir/runner" #.fsdefaults command -v krunner krunner EOF # Settings daemons - cat << EOF > "$_configdir/settings-daemon" + cat << EOF > "$_optconfigdir/settings-daemon" #.fsdefaults command -v xsettingsd xsettingsd @@ -241,7 +253,7 @@ command -v gnome-settings-daemon gnome-settings-daemon EOF # System statistics glances - cat << EOF > "$_configdir/stat-glances" + cat << EOF > "$_optconfigdir/stat-glances" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -251,7 +263,7 @@ command -v conky && [ -r "\${XDG_CONFIG_HOME:-$HOME/.config}/conky/conky.conf" ] sleep 5 && conky EOF # Wallpaper setters - cat << EOF > "$_configdir/wallpaper" + cat << EOF > "$_optconfigdir/wallpaper" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -261,7 +273,7 @@ command -v nitrogen nitrogen --restore EOF # Window managers - cat << EOF > "$_configdir/wm" + cat << EOF > "$_optconfigdir/wm" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -327,125 +339,70 @@ command -v tinywm tinywm EOF } -step_printhelp() { - cat << EOF -Usage: $(basename -- "$0") [OPTION...] -Start or generate a desktop environment configuration - - -h Show this help text - -g Generate a default configuration. This will clobber - -d Perform a dry run. This will print the results of all decisions - without executing them. - -Additionally, $(basename -- "$0") responds to the following environment variables: - - FS_DIEONWM If nonempty, end the session when the WM dies. This is useful - both for preventing session lock-ins and for using built-in WM - features to log out of the session. - FS_NOLOG If nonempty, create no log files - FS_NOWAITWM If nonempty, skip waiting on the WM to initialize - -Copyright (c) 2019 rehashedsalt@cock.li -Licensed under the MIT License -https://gitlab.com/rehashedsalt/firestarter -EOF -} -step_check() { - if [ -z "$_dryrun" ] && [ -z "$_generate" ]; then - for pid in $(pgrep firestarter); do - # Skip invalid PIDs - ! [ -d "/proc/$pid" ] && continue - # If it's not our session then who cares - [ "$_sessionid" != "$(< "/proc/$pid/sessionid")" ] && continue - if [ "$pid" != "$BASHPID" ]; then - err "Firestarter is already running: $pid" 40 - fi - done - fi - if ! [ -d "$HOME" ] || ! [ -r "$HOME" ]; then - err "Inaccessible home directory: \"$HOME\"" 54 - fi - if ! [ -d "$_configdir" ]; then - if ! mkdir -p "$_configdir" > /dev/null 2>&1; then - err "Failed to create configuration directory: \"$_configdir\"" 52 - fi - fi - return 0 -} step_preexecute() { # Special things that can't use simple configuration files - [ -n "$_dryrun" ] && return 0 - - # Xsessionrc - [ -r "$HOME/.xsessionrc" ] && source "$HOME/.xsessionrc" - - # Exports + [ -n "$_optdryrun" ] && return 0 + [ -r "$HOME/.xsessionrc" ] && . "$HOME/.xsessionrc" export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:-firestarter}" - # dbus if \ [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && \ [ -n "$XDG_RUNTIME_DIR" ] && \ [ "$XDG_RUNTIME_DIR" = "/run/user/$(id -u)" ] && \ [ -S "$XDG_RUNTIME_DIR/bus" ]; then - # We already have a bus started, use it + # We already have a bus started; use it export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus" hasdbus=1 elif \ [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && \ has dbus-launch; then - # We have dbus but haven't started up a bus yet - eval "$(dbus-launch --exit-with-session --sh-syntax)" + # We have dbus but haven't started it yet + eval "$(dbus-laucnh --exit-with-session --sh-syntax)" hasdbus=1 else - log "Did not start dbus; some applications may misbehave" + warn "Did not start dbus; some applications may misbehave" fi if [ -n "$hasdbus" ]; then - if has dbus-update-activation-environment; then - dbus-update-activation-environment --verbose --systemd --all > /dev/null 2>&1 - fi + has dbus-update-activation-environment && \ + dbus-update-activation-environment --verbose --systemd --all >/dev/null 2>&1 fi unset hasdbus - # kcminit/Qt settings if has kcminit; then log "Initializing KDE Control Module settings" - kcminit > /dev/null 2>&1 + kcminit >/dev/null 2>&1 export XDG_CURRENT_DESKTOP="KDE" elif has qt5ct; then - log "Integrating qt5ct" + log "Initializing qt5ct" if [ -z "$QT_QPA_PLATFORMTHEME" ]; then export QT_QPA_PLATFORMTHEME="qt5ct" - log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" + log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" 2 else - log "Using existing theme setting \"$QT_QPA_PLATFORMTHEME\"" + log "Using existing theme setting \"$QT_QPA_PLATFORMTHEME\"" 2 fi if [ -z "$QT_AUTO_SCREEN_SCALE_FACTOR" ]; then export QT_AUTO_SCREEN_SCALE_FACTOR="0" - log "Exporting QT_AUTO_SCREEN_SCALE_FACTOR as $QT_AUTO_SCREEN_SCALE_FACTOR" + log "Exporting QT_AUTO_SCREEN_SCALE_FACTOR as \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" 2 else - log "Using existing scale setting \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" + log "Using existing scale factor \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" 2 fi fi - # xhost if has xhost; then - if xhost +si:localuser:"$(id -un)" > /dev/null 2>&1; then - log "Session can be accessed in other sessions by this user" + if xhost +si:localuser:"$(id -un)" >/dev/null 2>&1; then + log "Session open to other sessions by this user" else - log "Failed to open session up via xhost" + warn "Failed to open session via xhost" fi fi - # xresources if [ -n "$DISPLAY" ] && has xrdb && [ -r "$HOME/.Xresources" ]; then - if xrdb "$HOME/.Xresources" > /dev/null 2>&1; then + if xrdb "$HOME/.Xresources" >/dev/null 2>&1; then log "Loaded in .Xresources" else - log "Failed to load .Xresources" + warn "Failed to load .Xresources" fi fi - # xset if has xset; then log "Disabling bell" @@ -453,84 +410,81 @@ step_preexecute() { fi } step_execute() { - # Ensure we can log if we have to - if [ -n "$FS_NOLOG" ]; then - log "No logs will be created per FS_NOLOG" - elif ! [ -d "$_logdir" ]; then - if ! mkdir -p "$_logdir" > /dev/null 2>&1; then - err "Failed to create log directory: \"$_logdir\"" 53 + # Parse out our defaults lists and execute their targets + if ! [ -d "$_optlogdir" ]; then + if ! mkdir -p "$_optlogdir" >/dev/null 2>&1; then + error "Failed to create log directory: \"$_optlogdir\"" 53 fi fi - # Parse configs - for file in "$_configdir"/*; do + for file in "$_optconfigdir"/*; do if ! [ -e "$file" ]; then log "No configuration files found; generating defaults" - step_generate + genconfigs step_execute - break + return fi + # Skip our logs directory + [ "$_optlogdir" == "$file" ] && continue local filename="$(basename -- "$file")" - local logfile="$_logdir/$filename" - [ -n "$FS_NOLOG" ] && logfile="/dev/null" + local logfile="$_optlogdir/$filename.log" if gettarget "$file"; then # It's a defaults file with a selected target target="$_return" - log "Found target for \"$filename\": \"$target\"" - if [ -z "$_dryrun" ]; then - if [ -f "$logfile" ]; then - [ -f "$logfile.old" ] && rm "$logfile.old" - mv "$logfile" "$logfile.old" - fi - bash -c "$target" > "$logfile" 2>&1 & + log "Found target for $filename: \"$target\"" + [ -n "$_optdryrun" ] && continue + if [ -f "$logfile" ]; then + [ -f "$logfile.old" ] && rm "$logfile.old" + mv "$logfile" "$logfile.old" fi + bash -c "$target" > "$logfile" 2>&1 & elif [ $? = 50 ] && [ -x "$file" ]; then - # It's a shell script or something - log "Executing file straight out: \"$filename\"" - if [ -z "$_dryrun" ]; then - "$file" > "$logfile" 2>&1 & - fi + # It's a shell script or executable symlink + log "Executing file: \"$filename\"" + [ -n "$_optdryrun" ] && continue + "$file" > "$logfile" 2>&1 & else - err "Could not execute file: \"$filename\"" + warn "Could not execute file: \"$filename\"" fi done } step_postexecute() { # Wait for the WM to initialize, if one was found and we have the tools - if [ -z "$FS_NOWAITWM" ] && gettarget "$_configdir/wm" && has xprop && has grep; then + if [ -z "$FS_NOWAITWM" ] && gettarget "$_configdir/wm" && has xprop grep; then log "Waiting for WM to initialize: \"$_return\"" for (( i=0; i<10; i++ )); do line="$(xprop -root | grep ^_NET_WM_NAME | grep -o '"\S*"$')" if [ -n "$line" ]; then - log "WM has intiailized, _NET_WM_NAME atom reads: $line" + log "WM has initialized, _NET_WM_NAME atom reads: $line" break fi read -t 1 -u 1023 done fi - # Dumb Polybar workaround + # Dumb polybar workaround killall polybar -SIGUSR1 - # Execute a user script if it exists - if [ -r "$_firestarterrc" ] && [ -z "$_dryrun" ]; then - log "Executing .firestarterrc" - "$_firestarterrc" + # Execute a user rc if it exists + local firestarterrc="$HOME/.firestarterrc" + if [ -r "$firestarterrc" ] && [ -z "$dryrun" ]; then + log "Executing rc script: $firestarterrc" + "$firestarterrc" fi # Start XDG autostarters, if they exist - if [ -z "$_dryrun" ]; then + if [ -z "$_optdryrun" ]; then if has dex; then - dex -a > /dev/null 2>&1 + dex -a >/dev/null 2>&1 elif has fbautostart; then fbautostart > /dev/null 2>&1 elif has xdg-autostart; then xdg-autostart ${XDG_CURRENT_DESKTOP:-firestarter} else - err "Could not find an XDG autostarter" + warn "Could not find an XDG autostarter" fi fi } step_wait() { - [ -n "$_dryrun" ] && exit 0 + [ -n "$_optdryrun" ] && exit 0 trap step_logout EXIT - if [ -n "$FS_DIEONWM" ] && gettarget "$_configdir/wm" && has readlink && has "$_return"; then + if [ -n "$FS_DIEONWM" ] && gettarget "$_configdir/wm" && has readlink "$_return"; then target="$(command -v "$_return")" for job in $(jobs -p); do if [ "$target" = "$(readlink /proc/$job/exe)" ]; then @@ -539,7 +493,7 @@ step_wait() { exit 0 fi done - err "Could not find WM: \"$target\"" + warn "Could not find WM: \"$target\"" fi log "Waiting for programs to exit" wait @@ -553,45 +507,108 @@ step_logout() { loginctl terminate-session "$_sessionid" fi else - # Otherwise just brute it + # Otherwise just brute it out kill $(jobs -p) fi return 0 } +printhelp() { + cat << EOF +Usage: $_name [OPTION]... + + -d Perform a dry run; print what programs would have been + executed instead of doing so + -g Regenerate default configs. This will clobber + -h Print this help text + -v Print more status messages. Stacks + +Environment Variables: + + FS_DIEONWM If nonempty, end the session when the WM dies. + FS_NOWAITWM If nonempty, skip waiting for the WM to initialize + +Copyright (c) 2019 rehashedsalt@cock.li +Licensed under the MIT license +EOF +} + # Main main() { - while getopts ":dgh" opt; do - case $opt in - d) - _dryrun=1 - ;; - g) - _generate=1 - ;; - h) - step_printhelp - exit $? - ;; - *) - err "Unrecognized argument: \"$OPTARG\"" 50 - ;; - :) - err "Invalid option: \"$OPTARG\"" 51 - ;; - esac + # Parse out arguments + while [ -n "$1" ]; do + # Parse out flags + while getopts ":dghv" opt; do + case $opt in + d) + _optdryrun=1 + ;; + g) + _optpregen=1 + ;; + h) + _opthelp=1 + ;; + v) + _optverbose+=1 + ;; + :) + error "Option requires argument: -$OPTARG" 2 + ;; + *) + error "Invalid option: -$OPTARG" 2 + ;; + esac + done + # Store arguments + shift $((OPTIND - 1)) + if [ -n "$1" ]; then + _args+=("$1") + shift + fi + unset OPTIND done - step_check - if [ -n "$_generate" ]; then - step_generate - else - [ -n "$_dryrun" ] && log "Performing a dry run" - step_preexecute - step_execute - step_postexecute - step_wait + # Early hook for help + [ -n "$_opthelp" ] && printhelp && exit 0 + # Early hook for generating configs + [ -n "$_optpregen" ] && genconfigs && exit $? + # Ensure our running environment is sane and that we're not about to nest + if [ -z "$_optdryrun" ] && [ -z "$_optpregen" ]; then + for pid in $(pgrep firestarter); do + # Skip invalid PIDs + ! [ -d "$/proc/$pid" ] && continue + # If it's not our session then who cares + [ "$_sessionid" != "$(< "/proc/$pid/sessionid")" ] && continue + # If it's us then who cares + [ "$pid" == "$BASHPID" ] && continue + # We care + error "Firestarter is already running: $pid" 40 + done fi - return 0 + if ! [ -d "$HOME" ] || ! [ -r "$HOME" ]; then + error "Home directory not found or inaccessable: \"$HOME\"" 54 + fi + if ! [ -d "$_optconfigdir" ]; then + if [ -f "$_optconfigdir" ]; then + error "Config directory is a file, should be directory: \"$_optconfigdir\"" 52 + fi + if ! mkdir -p "$_optconfigdir" > /dev/null 2>&1; then + error "Failed to find or create config directory: \"$_optconfigdir\"" 52 + fi + fi + # Validate core program dependencies + log "Validating dependencies" 2 + if ! has basename; then + error "Failed to find program: $_return" 1 + fi + + # Do the do + [ -n "$_optdryrun" ] && log "Performing a dry run" + step_preexecute + step_execute + step_wait + step_logout + exit 0 } main "$@" diff --git a/firestarter2.bash b/firestarter-old similarity index 57% rename from firestarter2.bash rename to firestarter-old index 0c679bd..50d5e0b 100755 --- a/firestarter2.bash +++ b/firestarter-old @@ -6,76 +6,60 @@ # # Distributed under terms of the MIT license. # -set -e -# Read-only set-once variables -declare -r _name="$(basename -- "$0")" -declare -r _sessionid="$(< /proc/self/sessionid)" -# Options -declare _optconfigdir="${XDG_CONFIG_HOME:-$HOME/.config}/${_name}" -declare _optdryrun -declare _optlogdir="$_optconfigdir/logs" -declare _optpregen -declare -i _opthelp -declare -i _optverbose -# Working variables -declare -a _args -declare _return +# "Globals" +_name="firestarter" +_configdir="${XDG_CONFIG_HOME:-$HOME/.config}/$_name" +_logdir="${XDG_DATA_HOME:-$HOME/.local/share}/$_name/logs" +_firestarterrc="$HOME/.firestarterrc" +_sessionid="$(< /proc/self/sessionid)" +# Dummy fd for read sleeping +exec 1023<> <(:) -# Helper functions +# Basic functions +print() { + # Write a message to STDOUT without a name attached + # 1: That message + [ -z "$1" ] && return 1 + printf "%s\\n" \ + "$1" +} log() { - # Print a line to the terminal if _optverbose is greater than $2 - # $2 defaults to 0 - # loglevel 0: Daily-use messages - # loglevel 1: Detailed but not quite debugging - # loglevel 2: Definitely debugging + # Write a message to STDOUT + # 1: That message [ -z "$1" ] && return 1 - if (( _optverbose >= ${2:-0} )); then - printf "%s\\n" "$1" - fi + printf "%s log: %s\\n" \ + "$_name" \ + "$1" >&1 } -warn() { - # Print a yellow line to the terminal, respecting _optverbose +err() { + # Write a message to STDERR, also exit if arg 2 is specified + # 1: That message + # 2: An optional exit code (will only exit if provided) [ -z "$1" ] && return 1 - if (( _optverbose >= ${2:-0} )); then - if [ -t 1 ]; then - printf "\\e[33m%s\\e[0m\\n" "$1" - else - printf "WARN: %s\\n" "$1" - fi - fi -} -error() { - # Print a red line to the terminal, exit if $2 is specified - [ -z "$1" ] && return 1 - if [ -t 2 ]; then - printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2 + printf "%s err: %s\\n" \ + "$_name" \ + "$1" >&2 + [ -z "$2" ] && return 0 + if ! [ "$2" -ge "0" ] > /dev/null 2>&1; then + err "Attempted to exit with malformed exit code \"$2\"" 10 else - printf "ERROR: %s\\n" "$1" 1>&2 + exit "$2" fi - [ -z "$2" ] && return - exit "${2:-1}" } has() { - # Parse out all arguments and try to find them in path - # If an argument cannot be found, set _return and fail - for prog in "$@"; do - if ! command -v "$prog" > /dev/null 2>&1; then - _return="$prog" - return 1 - fi - done - return 0 -} - -# Core program functions -gettarget() { - # Parse a defaults file to get a target program + # See if a program exists in $PATH + # 1: A program + [ -z "$1" ] && return 1 + command -v "$1" > /dev/null 2>&1 +} +gettarget() { + # Parse a defaults file to get the target program + # 1: A defaults file [ -z "$1" ] && return 1 - [ -d "$1" ] && return 1 [ -r "$1" ] || return 1 - # Every odd line is a condition - # Every even one is a target + # Every odd line is the check line + # Every even one is the exec line local firstline while read -r checkline; do if [ -z "$firstline" ]; then @@ -86,7 +70,9 @@ gettarget() { return 50 fi fi - [ "${checkline#"#"}" != "$checkline" ] && continue + if [ "${checkline#"#"}" != "$checkline" ]; then + continue + fi read -r execline if bash -c "$checkline" > /dev/null 2>&1; then _return="$execline" @@ -97,17 +83,19 @@ gettarget() { done < "$1" return 2 } -genconfigs() { - log "Creating default config setup in \"$_optconfigdir\"" + +# Steps in execution +step_generate() { + log "Creating default config setup in \"$_configdir\"" log "See firestarter -h for more information" # Audio daemon - cat << EOF > "$_optconfigdir/audio-daemon" + cat << EOF > "$_configdir/audio-daemon" #.fsdefaults command -v pulseaudio pulseaudio EOF # Information bars - cat << EOF > "$_optconfigdir/bar" + cat << EOF > "$_configdir/bar" #.fsdefaults command -v polybar && [ -r "$HOME/.config/polybar/launch.sh" ] "$HOME/.config/polybar/launch.sh" @@ -123,7 +111,7 @@ command -v xfce4-panel xfce4-panel EOF # Blue light filter - cat << EOF > "$_optconfigdir/blue-light-filter" + cat << EOF > "$_configdir/blue-light-filter" #.fsdefaults command -v redshift-gtk redshift-gtk @@ -131,7 +119,7 @@ command -v redshift redshift EOF # Compositor - cat << EOF > "$_optconfigdir/compositor" + cat << EOF > "$_configdir/compositor" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -143,7 +131,7 @@ command -v xcompmgr xcompmgr EOF # Polkit authentication agents - cat << EOF > "$_optconfigdir/polkit-agent" + cat << EOF > "$_configdir/polkit-agent" #.fsdefaults command -v lxqt-policykit-agent lxqt-policykit-agent @@ -188,7 +176,7 @@ polkit-efl-authentication-agent-1 /usr/libexec/polkit-gnome-authentication-agent-1 EOF # Hotkey daemon - cat << EOF > "$_optconfigdir/hotkey-daemon" + cat << EOF > "$_configdir/hotkey-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -198,13 +186,13 @@ command -v lxqt-globalkeysd lxqt-globalkeysd EOF # Network daemon - cat << EOF > "$_optconfigdir/network-daemon" + cat << EOF > "$_configdir/network-daemon" #.fsdefaults command -v nm-applet nm-applet EOF # Notification daemon - cat << EOF > "$_optconfigdir/notification-daemon" + cat << EOF > "$_configdir/notification-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -214,7 +202,7 @@ command -v lxqt-notificationd notificationd EOF # Power daemons - cat << EOF > "$_optconfigdir/power-daemon" + cat << EOF > "$_configdir/power-daemon" #.fsdefaults command -v batterymon batterymon @@ -231,13 +219,13 @@ gnome-power-manager EOF # Runners # Note that rofi is not a daemon and is not included here - cat << EOF > "$_optconfigdir/runner" + cat << EOF > "$_configdir/runner" #.fsdefaults command -v krunner krunner EOF # Settings daemons - cat << EOF > "$_optconfigdir/settings-daemon" + cat << EOF > "$_configdir/settings-daemon" #.fsdefaults command -v xsettingsd xsettingsd @@ -253,7 +241,7 @@ command -v gnome-settings-daemon gnome-settings-daemon EOF # System statistics glances - cat << EOF > "$_optconfigdir/stat-glances" + cat << EOF > "$_configdir/stat-glances" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -263,7 +251,7 @@ command -v conky && [ -r "\${XDG_CONFIG_HOME:-$HOME/.config}/conky/conky.conf" ] sleep 5 && conky EOF # Wallpaper setters - cat << EOF > "$_optconfigdir/wallpaper" + cat << EOF > "$_configdir/wallpaper" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -273,7 +261,7 @@ command -v nitrogen nitrogen --restore EOF # Window managers - cat << EOF > "$_optconfigdir/wm" + cat << EOF > "$_configdir/wm" #.fsdefaults [ -z "\$DISPLAY" ] : @@ -339,70 +327,125 @@ command -v tinywm tinywm EOF } +step_printhelp() { + cat << EOF +Usage: $(basename -- "$0") [OPTION...] +Start or generate a desktop environment configuration + + -h Show this help text + -g Generate a default configuration. This will clobber + -d Perform a dry run. This will print the results of all decisions + without executing them. + +Additionally, $(basename -- "$0") responds to the following environment variables: + + FS_DIEONWM If nonempty, end the session when the WM dies. This is useful + both for preventing session lock-ins and for using built-in WM + features to log out of the session. + FS_NOLOG If nonempty, create no log files + FS_NOWAITWM If nonempty, skip waiting on the WM to initialize + +Copyright (c) 2019 rehashedsalt@cock.li +Licensed under the MIT License +https://gitlab.com/rehashedsalt/firestarter +EOF +} +step_check() { + if [ -z "$_dryrun" ] && [ -z "$_generate" ]; then + for pid in $(pgrep firestarter); do + # Skip invalid PIDs + ! [ -d "/proc/$pid" ] && continue + # If it's not our session then who cares + [ "$_sessionid" != "$(< "/proc/$pid/sessionid")" ] && continue + if [ "$pid" != "$BASHPID" ]; then + err "Firestarter is already running: $pid" 40 + fi + done + fi + if ! [ -d "$HOME" ] || ! [ -r "$HOME" ]; then + err "Inaccessible home directory: \"$HOME\"" 54 + fi + if ! [ -d "$_configdir" ]; then + if ! mkdir -p "$_configdir" > /dev/null 2>&1; then + err "Failed to create configuration directory: \"$_configdir\"" 52 + fi + fi + return 0 +} step_preexecute() { # Special things that can't use simple configuration files - [ -n "$_optdryrun" ] && return 0 - [ -r "$HOME/.xsessionrc" ] && . "$HOME/.xsessionrc" + [ -n "$_dryrun" ] && return 0 + + # Xsessionrc + [ -r "$HOME/.xsessionrc" ] && source "$HOME/.xsessionrc" + + # Exports export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:-firestarter}" + # dbus if \ [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && \ [ -n "$XDG_RUNTIME_DIR" ] && \ [ "$XDG_RUNTIME_DIR" = "/run/user/$(id -u)" ] && \ [ -S "$XDG_RUNTIME_DIR/bus" ]; then - # We already have a bus started; use it + # We already have a bus started, use it export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus" hasdbus=1 elif \ [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && \ has dbus-launch; then - # We have dbus but haven't started it yet - eval "$(dbus-laucnh --exit-with-session --sh-syntax)" + # We have dbus but haven't started up a bus yet + eval "$(dbus-launch --exit-with-session --sh-syntax)" hasdbus=1 else - warn "Did not start dbus; some applications may misbehave" + log "Did not start dbus; some applications may misbehave" fi if [ -n "$hasdbus" ]; then - has dbus-update-activation-environment && \ - dbus-update-activation-environment --verbose --systemd --all >/dev/null 2>&1 + if has dbus-update-activation-environment; then + dbus-update-activation-environment --verbose --systemd --all > /dev/null 2>&1 + fi fi unset hasdbus + # kcminit/Qt settings if has kcminit; then log "Initializing KDE Control Module settings" - kcminit >/dev/null 2>&1 + kcminit > /dev/null 2>&1 export XDG_CURRENT_DESKTOP="KDE" elif has qt5ct; then - log "Initializing qt5ct" + log "Integrating qt5ct" if [ -z "$QT_QPA_PLATFORMTHEME" ]; then export QT_QPA_PLATFORMTHEME="qt5ct" - log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" 2 + log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" else - log "Using existing theme setting \"$QT_QPA_PLATFORMTHEME\"" 2 + log "Using existing theme setting \"$QT_QPA_PLATFORMTHEME\"" fi if [ -z "$QT_AUTO_SCREEN_SCALE_FACTOR" ]; then export QT_AUTO_SCREEN_SCALE_FACTOR="0" - log "Exporting QT_AUTO_SCREEN_SCALE_FACTOR as \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" 2 + log "Exporting QT_AUTO_SCREEN_SCALE_FACTOR as $QT_AUTO_SCREEN_SCALE_FACTOR" else - log "Using existing scale factor \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" 2 + log "Using existing scale setting \"$QT_AUTO_SCREEN_SCALE_FACTOR\"" fi fi + # xhost if has xhost; then - if xhost +si:localuser:"$(id -un)" >/dev/null 2>&1; then - log "Session open to other sessions by this user" + if xhost +si:localuser:"$(id -un)" > /dev/null 2>&1; then + log "Session can be accessed in other sessions by this user" else - warn "Failed to open session via xhost" + log "Failed to open session up via xhost" fi fi + # xresources if [ -n "$DISPLAY" ] && has xrdb && [ -r "$HOME/.Xresources" ]; then - if xrdb "$HOME/.Xresources" >/dev/null 2>&1; then + if xrdb "$HOME/.Xresources" > /dev/null 2>&1; then log "Loaded in .Xresources" else - warn "Failed to load .Xresources" + log "Failed to load .Xresources" fi fi + # xset if has xset; then log "Disabling bell" @@ -410,81 +453,84 @@ step_preexecute() { fi } step_execute() { - # Parse out our defaults lists and execute their targets - if ! [ -d "$_optlogdir" ]; then - if ! mkdir -p "$_optlogdir" >/dev/null 2>&1; then - error "Failed to create log directory: \"$_optlogdir\"" 53 + # Ensure we can log if we have to + if [ -n "$FS_NOLOG" ]; then + log "No logs will be created per FS_NOLOG" + elif ! [ -d "$_logdir" ]; then + if ! mkdir -p "$_logdir" > /dev/null 2>&1; then + err "Failed to create log directory: \"$_logdir\"" 53 fi fi - for file in "$_optconfigdir"/*; do + # Parse configs + for file in "$_configdir"/*; do if ! [ -e "$file" ]; then log "No configuration files found; generating defaults" - genconfigs + step_generate step_execute - return + break fi - # Skip our logs directory - [ "$_optlogdir" == "$file" ] && continue local filename="$(basename -- "$file")" - local logfile="$_optlogdir/$filename.log" + local logfile="$_logdir/$filename" + [ -n "$FS_NOLOG" ] && logfile="/dev/null" if gettarget "$file"; then # It's a defaults file with a selected target target="$_return" - log "Found target for $filename: \"$target\"" - [ -n "$_optdryrun" ] && continue - if [ -f "$logfile" ]; then - [ -f "$logfile.old" ] && rm "$logfile.old" - mv "$logfile" "$logfile.old" + log "Found target for \"$filename\": \"$target\"" + if [ -z "$_dryrun" ]; then + if [ -f "$logfile" ]; then + [ -f "$logfile.old" ] && rm "$logfile.old" + mv "$logfile" "$logfile.old" + fi + bash -c "$target" > "$logfile" 2>&1 & fi - bash -c "$target" > "$logfile" 2>&1 & elif [ $? = 50 ] && [ -x "$file" ]; then - # It's a shell script or executable symlink - log "Executing file: \"$filename\"" - [ -n "$_optdryrun" ] && continue - "$file" > "$logfile" 2>&1 & + # It's a shell script or something + log "Executing file straight out: \"$filename\"" + if [ -z "$_dryrun" ]; then + "$file" > "$logfile" 2>&1 & + fi else - warn "Could not execute file: \"$filename\"" + err "Could not execute file: \"$filename\"" fi done } step_postexecute() { # Wait for the WM to initialize, if one was found and we have the tools - if [ -z "$FS_NOWAITWM" ] && gettarget "$_configdir/wm" && has xprop grep; then + if [ -z "$FS_NOWAITWM" ] && gettarget "$_configdir/wm" && has xprop && has grep; then log "Waiting for WM to initialize: \"$_return\"" for (( i=0; i<10; i++ )); do line="$(xprop -root | grep ^_NET_WM_NAME | grep -o '"\S*"$')" if [ -n "$line" ]; then - log "WM has initialized, _NET_WM_NAME atom reads: $line" + log "WM has intiailized, _NET_WM_NAME atom reads: $line" break fi read -t 1 -u 1023 done fi - # Dumb polybar workaround + # Dumb Polybar workaround killall polybar -SIGUSR1 - # Execute a user rc if it exists - local firestarterrc="$HOME/.firestarterrc" - if [ -r "$firestarterrc" ] && [ -z "$dryrun" ]; then - log "Executing rc script: $firestarterrc" - "$firestarterrc" + # Execute a user script if it exists + if [ -r "$_firestarterrc" ] && [ -z "$_dryrun" ]; then + log "Executing .firestarterrc" + "$_firestarterrc" fi # Start XDG autostarters, if they exist - if [ -z "$_optdryrun" ]; then + if [ -z "$_dryrun" ]; then if has dex; then - dex -a >/dev/null 2>&1 + dex -a > /dev/null 2>&1 elif has fbautostart; then fbautostart > /dev/null 2>&1 elif has xdg-autostart; then xdg-autostart ${XDG_CURRENT_DESKTOP:-firestarter} else - warn "Could not find an XDG autostarter" + err "Could not find an XDG autostarter" fi fi } step_wait() { - [ -n "$_optdryrun" ] && exit 0 + [ -n "$_dryrun" ] && exit 0 trap step_logout EXIT - if [ -n "$FS_DIEONWM" ] && gettarget "$_configdir/wm" && has readlink "$_return"; then + if [ -n "$FS_DIEONWM" ] && gettarget "$_configdir/wm" && has readlink && has "$_return"; then target="$(command -v "$_return")" for job in $(jobs -p); do if [ "$target" = "$(readlink /proc/$job/exe)" ]; then @@ -493,7 +539,7 @@ step_wait() { exit 0 fi done - warn "Could not find WM: \"$target\"" + err "Could not find WM: \"$target\"" fi log "Waiting for programs to exit" wait @@ -507,108 +553,45 @@ step_logout() { loginctl terminate-session "$_sessionid" fi else - # Otherwise just brute it out + # Otherwise just brute it kill $(jobs -p) fi return 0 } -printhelp() { - cat << EOF -Usage: $_name [OPTION]... - - -d Perform a dry run; print what programs would have been - executed instead of doing so - -g Regenerate default configs. This will clobber - -h Print this help text - -v Print more status messages. Stacks - -Environment Variables: - - FS_DIEONWM If nonempty, end the session when the WM dies. - FS_NOWAITWM If nonempty, skip waiting for the WM to initialize - -Copyright (c) 2019 rehashedsalt@cock.li -Licensed under the MIT license -EOF -} - # Main main() { - # Parse out arguments - while [ -n "$1" ]; do - # Parse out flags - while getopts ":dghv" opt; do - case $opt in - d) - _optdryrun=1 - ;; - g) - _optpregen=1 - ;; - h) - _opthelp=1 - ;; - v) - _optverbose+=1 - ;; - :) - error "Option requires argument: -$OPTARG" 2 - ;; - *) - error "Invalid option: -$OPTARG" 2 - ;; - esac - done - # Store arguments - shift $((OPTIND - 1)) - if [ -n "$1" ]; then - _args+=("$1") - shift - fi - unset OPTIND + while getopts ":dgh" opt; do + case $opt in + d) + _dryrun=1 + ;; + g) + _generate=1 + ;; + h) + step_printhelp + exit $? + ;; + *) + err "Unrecognized argument: \"$OPTARG\"" 50 + ;; + :) + err "Invalid option: \"$OPTARG\"" 51 + ;; + esac done - # Early hook for help - [ -n "$_opthelp" ] && printhelp && exit 0 - # Early hook for generating configs - [ -n "$_optpregen" ] && genconfigs && exit $? - # Ensure our running environment is sane and that we're not about to nest - if [ -z "$_optdryrun" ] && [ -z "$_optpregen" ]; then - for pid in $(pgrep firestarter); do - # Skip invalid PIDs - ! [ -d "$/proc/$pid" ] && continue - # If it's not our session then who cares - [ "$_sessionid" != "$(< "/proc/$pid/sessionid")" ] && continue - # If it's us then who cares - [ "$pid" == "$BASHPID" ] && continue - # We care - error "Firestarter is already running: $pid" 40 - done + step_check + if [ -n "$_generate" ]; then + step_generate + else + [ -n "$_dryrun" ] && log "Performing a dry run" + step_preexecute + step_execute + step_postexecute + step_wait fi - if ! [ -d "$HOME" ] || ! [ -r "$HOME" ]; then - error "Home directory not found or inaccessable: \"$HOME\"" 54 - fi - if ! [ -d "$_optconfigdir" ]; then - if [ -f "$_optconfigdir" ]; then - error "Config directory is a file, should be directory: \"$_optconfigdir\"" 52 - fi - if ! mkdir -p "$_optconfigdir" > /dev/null 2>&1; then - error "Failed to find or create config directory: \"$_optconfigdir\"" 52 - fi - fi - # Validate core program dependencies - log "Validating dependencies" 2 - if ! has basename; then - error "Failed to find program: $_return" 1 - fi - - # Do the do - [ -n "$_optdryrun" ] && log "Performing a dry run" - step_preexecute - step_execute - step_wait - step_logout - exit 0 + return 0 } main "$@"