#! /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 - %s\\n" "$(date -Iseconds)" "$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 [ -x "/usr/lib/$(uname -m)-linux-gnu/libexec/org_kde_powerdevil" ] /usr/lib/$(uname -m)-linux-gnu/libexec/org_kde_powerdevil 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 # xsetroot if has xsetroot; then log "Setting root window properties" xsetroot -cursor_name left_ptr 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 "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 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 if [ -n "$_optpregen" ]; then genconfigs exit $? fi # 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 "$@"