#! /bin/bash # # firestarter # A desktop environment startup script # Copyright (C) 2019 Vintage Salt # # Distributed under terms of the MIT license. # # "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<> <(:) # Basic functions print() { # Write a message to STDOUT without a name attached # 1: That message [ -z "$1" ] && return 1 printf "%s\\n" \ "$1" } log() { # Write a message to STDOUT # 1: That message [ -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" fi } has() { # 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 [ -r "$1" ] || return 1 # 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 if [ "$checkline" = "#.fsdefaults" ]; then firstline=1 continue else return 50 fi fi if [ "${checkline#"#"}" != "$checkline" ]; then continue fi read -r execline if bash -c "$checkline" > /dev/null 2>&1; then _return="$execline" return 0 else continue fi done < "$1" return 2 } # Steps in execution step_generate() { log "Creating default config setup in \"$_configdir\"" log "See firestarter -h for more information" # Audio daemon cat << EOF > "$_configdir/audio-daemon" #.fsdefaults command -v pulseaudio pulseaudio EOF # Information bars cat << EOF > "$_configdir/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 > "$_configdir/blue-light-filter" #.fsdefaults command -v redshift-gtk redshift-gtk command -v redshift redshift EOF # Compositor cat << EOF > "$_configdir/compositor" #.fsdefaults [ -z "\$DISPLAY" ] : command -v unagi unagi command -v compton compton command -v xcompmgr xcompmgr EOF # Polkit authentication agents cat << EOF > "$_configdir/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 > "$_configdir/hotkey-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : command -v sxhkd sxhkd command -v lxqt-globalkeysd lxqt-globalkeysd EOF # Network daemon cat << EOF > "$_configdir/network-daemon" #.fsdefaults command -v nm-applet nm-applet EOF # Notification daemon cat << EOF > "$_configdir/notification-daemon" #.fsdefaults [ -z "\$DISPLAY" ] : command -v dunst dunst command -v lxqt-notificationd notificationd EOF # Power daemons cat << EOF > "$_configdir/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 > "$_configdir/runner" #.fsdefaults command -v krunner krunner EOF # Settings daemons cat << EOF > "$_configdir/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 > "$_configdir/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 > "$_configdir/wallpaper" #.fsdefaults [ -z "\$DISPLAY" ] : command -v feh && [ -r "$HOME/.fehbg" ] ~/.fehbg command -v nitrogen nitrogen --restore EOF # Window managers cat << EOF > "$_configdir/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_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 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 up a bus yet eval "$(dbus-launch --exit-with-session --sh-syntax)" hasdbus=1 else log "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 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 "Integrating qt5ct" if [ -z "$QT_QPA_PLATFORMTHEME" ]; then export QT_QPA_PLATFORMTHEME="qt5ct" log "Exporting QT_QPA_PLATFORMTHEME as \"$QT_QPA_PLATFORMTHEME\"" else 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" else 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 can be accessed in other sessions by this user" else 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 log "Loaded in .Xresources" else log "Failed to load .Xresources" fi fi # xset if has xset; then log "Disabling bell" xset -b 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 fi fi # Parse configs for file in "$_configdir"/*; do if ! [ -e "$file" ]; then log "No configuration files found; generating defaults" step_generate step_execute break fi local filename="$(basename -- "$file")" 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\"" 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 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 else 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 && 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 intiailized, _NET_WM_NAME atom reads: $line" break fi read -t 1 -u 1023 done fi # Dumb Polybar workaround killall polybar -SIGUSR1 # 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 "$_dryrun" ]; 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 err "Could not find an XDG autostarter" fi fi } step_wait() { [ -n "$_dryrun" ] && exit 0 trap step_logout EXIT 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 log "Waiting for WM to exit: \"$_return\"" wait "$job" exit 0 fi done err "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 kill $(jobs -p) fi return 0 } # 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 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 return 0 } main "$@"