diff --git a/firestarter2.bash b/firestarter2.bash new file mode 100755 index 0000000..cc6b518 --- /dev/null +++ b/firestarter2.bash @@ -0,0 +1,615 @@ +#! /bin/bash +# +# firestarter +# A desktop environment startup script +# Copyright (C) 2019 Vintage Salt +# +# 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 + +# Helper functions +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 + [ -z "$1" ] && return 1 + if (( _optverbose >= ${2:-0} )); then + printf "%s\\n" "$1" + fi +} +warn() { + # Print a yellow line to the terminal, respecting _optverbose + [ -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 + 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 a condition + # Every even one is a target + local firstline + while read -r checkline; do + if [ -z "$firstline" ]; then + if ["$checkline" = "#.fsdefaults" ]; then + firstline=1 + continue + else + return 50 + fi + fi + [ "${checkline#"#"}" != "$checkline" ] && continue + read -r execline + if bash -c "$checkline" > /dev/null 2>&1; then + _return="$execline" + return 0 + else + continue + fi + done < "$1" + return 2 +} +genconfigs() { + log "Creating default config setup in \"$_optconfigdir\"" + log "See firestarter -h for more information" + # Audio daemon + cat << EOF > "$_optconfigdir/audio-daemon" +#.fsdefaults +command -v pulseaudio +pulseaudio +EOF + # Information bars + cat << EOF > "$_optconfigdir/bar" +#.fsdefaults +command -v polybar && [ -r "$HOME/.config/polybar/launch.sh" ] +"$HOME/.config/polybar/launch.sh" +command -v tint2 +tint2 +command -v lxpanel +lxpanel +command -v lxqt-panel +lxqt-panel +command -v mate-panel +mate-panel +command -v xfce4-panel +xfce4-panel +EOF + # Blue light filter + cat << EOF > "$_optconfigdir/blue-light-filter" +#.fsdefaults +command -v redshift-gtk +redshift-gtk +command -v redshift +redshift +EOF + # Compositor + cat << EOF > "$_optconfigdir/compositor" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +command -v unagi +unagi +command -v compton +compton +command -v xcompmgr +xcompmgr +EOF + # Polkit authentication agents + cat << EOF > "$_optconfigdir/polkit-agent" +#.fsdefaults +command -v lxqt-policykit-agent +lxqt-policykit-agent +command -v lxpolkit +lxpolkit +command -v mate-polkit +mate-polkit +command -v polkit-efl-authentication-agent-1 +polkit-efl-authentication-agent-1 +[ -x "/usr/lib/ts-polkitagent" ] +/usr/lib/ts-polkitagent +[ -x "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1" ] +/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1 +[ -x "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1" ] +/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 +# Debian locations +# On generation time, your architecture is filled in here +[ -x "/usr/lib/$(uname -m)-linux-gnu/polkit-mate/polkit-mate-authentication-agent-1" ] +"/usr/lib/$(uname -m)-linux-gnu/polkit-mate/polkit-mate-authentication-agent-1" +[ -x "/usr/lib/$(uname -m)-linux-gnu/libexec/polkit-kde-authentication-agent-1" ] +"/usr/lib/$(uname -m)-linux-gnu/libexec/polkit-kde-authentication-agent-1" +# OpenSuSE locations +[ -x "/usr/lib/polkit-mate/polkit-mate-authentication-agent-1" ] +"/usr/lib/polkit-mate/polkit-mate-authentication-agent-1" +[ -x "/usr/lib/polkit-kde-authentication-agent-1" ] +"/usr/lib/polkit-kde-authentication-agent-1" +# Arch locations +[ -x "/usr/lib/mate-polkit/polkit-mate-authentication-agent-1" ] +/usr/lib/mate-polkit/polkit-mate-authentication-agent-1 +[ -x "/usr/lib/polkit-kde-authentication-agent-1" ] +/usr/lib/polkit-kde-authentication-agent-1 +# Fedora locations +[ -x "/usr/libexec/xfce-polkit" ] +/usr/libexec/xfce-polkit +[ -x "/usr/libexec/lxqt-policykit-agent" ] +/usr/libexec/lxqt-policykit-agent +[ -x "/usr/libexec/polkit-mate-authentication-agent-1" ] +/usr/libexec/polkit-mate-authentication-agent-1 +[ -x "/usr/libexec/kf5/polkit-kde-authentication-agent-1" ] +/usr/libexec/kf5/polkit-kde-authentication-agent-1 +[ -x "/usr/libexec/polkit-gnome-authentication-agent-1" ] +/usr/libexec/polkit-gnome-authentication-agent-1 +EOF + # Hotkey daemon + cat << EOF > "$_optconfigdir/hotkey-daemon" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +command -v sxhkd +sxhkd +command -v lxqt-globalkeysd +lxqt-globalkeysd +EOF + # Network daemon + cat << EOF > "$_optconfigdir/network-daemon" +#.fsdefaults +command -v nm-applet +nm-applet +EOF + # Notification daemon + cat << EOF > "$_optconfigdir/notification-daemon" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +command -v dunst +dunst +command -v lxqt-notificationd +notificationd +EOF + # Power daemons + cat << EOF > "$_optconfigdir/power-daemon" +#.fsdefaults +command -v batterymon +batterymon +command -v cbatticon +cbatticon +command -v lxqt-powermangement +lxqt-powermanagement +command -v xfce4-power-manager +xfce4-power-manager +command -v mate-power-manager +mate-power-manager +command -v gnome-power-manager +gnome-power-manager +EOF + # Runners + # Note that rofi is not a daemon and is not included here + cat << EOF > "$_optconfigdir/runner" +#.fsdefaults +command -v krunner +krunner +EOF + # Settings daemons + cat << EOF > "$_optconfigdir/settings-daemon" +#.fsdefaults +command -v xsettingsd +xsettingsd +command -v xsettings-kde +xsettingskde +command -v lxsettings-daemon +lxsettings-daemon +command -v xfsettingsd +xfsettingsd +command -v mate-settings-daemon +mate-settings-daemon +command -v gnome-settings-daemon +gnome-settings-daemon +EOF + # System statistics glances + cat << EOF > "$_optconfigdir/stat-glances" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +# Note: the dumb sleep hack is because Conky crashes with window_type override if the WM hasn't loaded yet +# This gives the WM ample time to load up +command -v conky && [ -r "\${XDG_CONFIG_HOME:-$HOME/.config}/conky/conky.conf" ] +sleep 5 && conky +EOF + # Wallpaper setters + cat << EOF > "$_optconfigdir/wallpaper" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +command -v feh && [ -r "$HOME/.fehbg" ] +~/.fehbg +command -v nitrogen +nitrogen --restore +EOF + # Window managers + cat << EOF > "$_optconfigdir/wm" +#.fsdefaults +[ -z "\$DISPLAY" ] +: +command -v 2bwm +2bwm +command -v aewm +aewm +command -v awesome +awesome +command -v bspwm +bspwm +command -v catwm +catwm +command -v cwm +cwm +command -v dwm +dwm +command -v evilwm +evilwm +command -v exwm +exwm +command -v fluxbox +fluxbox +command -v flwm +flwm +command -v fvwm +fvwm +command -v herbstluftwm +herbstluftwm +command -v i3 +i3 +command -v icewm +icewm +command -v jbwm +jbwm +command -v jwm +jwm +command -v lwm +lwm +command -v openbox +openbox +command -v pawm +pawm +command -v ratpoison +ratpoison +command -v twm +twm +command -v windowmaker +windowmaker +command -v wmii +wmii +command -v xmonad +xmonad +command -v xfwm4 +xfwm4 +command -v metacity +metacity +command -v mutter +mutter +command -v kwin +kwin +command -v tinywm +tinywm +EOF +} +step_preexecute() { + # Special things that can't use simple configuration files + [ -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 + 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)" + hasdbus=1 + else + warn "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 + fi + unset hasdbus + # kcminit/Qt settings + if has kcminit; then + log "Initializing KDE Control Module settings" + kcminit >/dev/null 2>&1 + export XDG_CURRENT_DESKTOP="KDE" + elif has qt5ct; then + log "Initializing qt5ct" + if [ -z "$QT_QPA_PLATFORMTHEME" ]; then + export QT_QPA_PLATFORMTHEME="qt5ct" + log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" 2 + else + 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\"" 2 + else + 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 open to other sessions by this user" + else + 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 + log "Loaded in .Xresources" + else + warn "Failed to load .Xresources" + fi + fi + # xset + if has xset; then + log "Disabling bell" + xset -b + 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 + fi + fi + for file in "$_optconfigdir"/*; do + if ! [ -e "$file" ]; then + log "No configuration files found; generating defaults" + genconfigs + step_execute + return + fi + # Skip our logs directory + [ "$_optlogdir" == "$file" ] && continue + local filename="$(basename -- "$file")" + local logfile="$_optlogdir/$filename.log" + if gettarget "$file"; then + # It's a defaults file with a selected target + target="$_return" + log "$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 executable symlink + log "Executing file: \"$filename\"" + [ -n "$_optdryrun" ] && continue + "$file" > "$logfile" 2>&1 & + else + 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 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" + break + fi + read -t 1 -u 1023 + done + fi + # 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" + fi + # Start XDG autostarters, if they exist + if [ -z "$_optdryrun" ]; then + if has dex; then + 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" + fi + fi +} +step_wait() { + [ -n "$_optdryrun" ] && exit 0 + trap step_logout EXIT + 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 + log "Waiting for WM to exit: \"$_return\"" + wait "$job" + exit 0 + fi + done + warn "Could not find WM: \"$target\"" + fi + log "Waiting for programs to exit" + wait + exit 0 +} +step_logout() { + log "Logging out" + if has loginctl; then + # Use loginctl if possible + if [ -n "$_sessionid" ]; then + loginctl terminate-session "$_sessionid" + fi + else + # 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() { + # 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 + # 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 + 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 "$@" +