Skip to content

Instantly share code, notes, and snippets.

@rawiriblundell
Last active October 21, 2021 09:48
Show Gist options
  • Save rawiriblundell/7e0a302b0ebfe121fedeedb22f521d87 to your computer and use it in GitHub Desktop.
Save rawiriblundell/7e0a302b0ebfe121fedeedb22f521d87 to your computer and use it in GitHub Desktop.
Script to switch between light and dark themes for Cinammon running on Mint
#!/bin/bash
# lightswitch - Switch themes between light and dark mode
# This is intended for Linux hosts using the Cinnamon desktop e.g. Mint.
export prog_name="${0##*/}"
export app_filename="/usr/share/applications/${prog_name}.desktop"
# Set our times to switch between light and dark themes (24hr time)
lights_on="${lights_on:-0900}"
lights_off="${lights_off:-2000}"
current_time=$(date +%H%M)
# Force these variables into decimal mode to prevent octal interpretation
lights_on=$(( 10#${lights_on} ))
lights_off=$(( 10#${lights_off} ))
current_time=$(( 10#${current_time} ))
# We set these here for consumption by set_theme_light|dark() and flip_theme()
dark_wmborders="${dark_wmborders:-CBlack}"
dark_icons="${dark_icons:-Papirus-Dark}"
dark_controls="${dark_controls:-CBlack}"
dark_mouse="${dark_mouse:-DMZ-White}"
dark_desktop="${dark_desktop:-CBlack}"
light_wmborders="${light_wmborders:-Cinnamox-Heather}"
light_icons="${light_icons:-Papirus-Light}"
light_controls="${light_controls:-ChromeOS}"
light_mouse="${light_mouse:-DMZ-White}"
light_desktop="${light_desktop:-Chinvat}"
# Define a full set of var validation functions
var_is_set() { [ "${1+x}" = "x" ] && [ "${#1}" -gt "0" ]; } # set and not null
var_is_unset() { [ -z "${1+x}" ]; } # unset
var_is_empty() { [ "${1+x}" = "x" ] && [ "${#1}" -eq "0" ]; } # set and null
var_is_blank() { var_is_unset "${1}" || var_is_empty "${1}"; } # unset, or set and null
create_desktop_shortcuts() {
cat << EOF > "${HOME}/Desktop/Light Mode.desktop"
[Desktop Entry]
Name=Light Mode
Exec=lightswitch -t light
Comment=
Terminal=false
Icon=sunny
Type=Application
EOF
cat << EOF > "${HOME}/Desktop/Dark Mode.desktop"
[Desktop Entry]
Name=Dark Mode
Exec=lightswitch -t dark
Comment=
Terminal=false
Icon=night-light-symbolic
Type=Application
EOF
chmod 755 "${HOME}/Desktop/Light Mode.desktop"
chmod 755 "${HOME}/Desktop/Dark Mode.desktop"
}
create_panel_shortcut() {
if [[ ! -f "${app_filename}" ]]; then
cat << EOF > "${app_filename}"
[Desktop Entry]
Name=${prog_name}
Comment=Switch between light and dark themes
GenericName=Theme changer
Exec=lightswitch -f
Terminal=false
Icon=sunny
Type=Application
EOF
fi
chown 0:"$(whoami)" "${app_filename}"
chmod 664 "${app_filename}"
}
add_panel_shortcut() {
(
cd "${HOME}/.cinnamon/configs/[email protected]" || return 1
# Google and my own system tells me this should be 3.json, but just in case
# TO-DO: Build in some smarts for ensuring the correct file is edited
local json_file="3.json"
# Ensure we don't already have an entry
if grep -q "${prog_name}.desktop" "${json_file}"; then
return 0
fi
# Back up the json file
cp "${json_file}" "${json_file}.bak.$(date +%Y%m%d)"
if command -v jq >/dev/null 2>&1; then
jq \
--arg prog_name "${prog_name}.desktop" \
-e \
'."pinned-apps".value=[$prog_name]+."pinned-apps".value' \
"${json_file}" > "${json_file}".tmp
mv "${json_file}".tmp "${json_file}"
# This 'sed' invocation will match the shortcut to nemo and insert it to the left
# TO-DO: make this match only the second instance of 'nemo.desktop'
else
sed -i "/\"nemo.desktop\",/i \ \"${prog_name}.desktop\",/" "${json_file}"
fi
printf -- '%s\n' \
"${prog_name}: Added shortcut to panel. To restore, run the following:" \
"mv ${PWD}/${json_file}.bak.$(date +%Y%m%d) ${PWD}/${json_file}"
)
}
get_cinnamon_spice() {
if ! command -v git >/dev/null 2>&1; then
printf -- '%s\n' "${prog_name} ERROR: 'git' is required to perform this"
exit 1
fi
[[ ! -d "${HOME}"/.cinnamon_spices_themes ]] && mkdir -p "${HOME}"/.cinnamon_spices_themes
if [[ ! -f "${HOME}"/.cinnamon_spices_themes/README.md ]]; then
git clone https://github.com/linuxmint/cinnamon-spices-themes.git "${HOME}"/.cinnamon_spices_themes/
fi
(
cd "${HOME}"/.cinnamon_spices_themes || return 1
git pull
for theme in *; do
case "${theme}" in
(README.md|.git|.github) : ;;
(*)
cp -R "${theme}/files/${theme}/" "${HOME}/.themes/"
;;
esac
done
)
}
get_desktop() { gsettings get org.cinnamon.theme name; }
get_icons() { gsettings get org.cinnamon.desktop.interface icon-theme; }
get_mouse() { gsettings get org.cinnamon.desktop.interface cursor-theme; }
get_controls() { gsettings get org.cinnamon.desktop.interface gtk-theme; }
get_wallpaper() { gsettings get org.cinnamon.desktop.background picture-uri; }
get_wmborders() { gsettings get org.cinnamon.desktop.wm.preferences theme; }
list_themes() {
printf -- '>>>> %s\n' "Current settings:"
printf -- '%s\n' \
"Window Borders: $(get_wmborders)" \
"Icons: $(get_icons)" \
"Controls: $(get_controls)" \
"Mouse Pointer: $(get_mouse)" \
"Desktop: $(get_desktop)" \
"Wallpaper: $(get_wallpaper)"
printf -- '>>>> %s\n' "Available themes:"
find /usr/share/themes/ "${HOME}"/.themes/ -maxdepth 1 -printf '%P\n' |
grep . |
sort |
column
}
set_desktop() {
gsettings set org.cinnamon.theme name "${1:?No theme declared}"
}
set_icons() {
gsettings set org.cinnamon.desktop.interface icon-theme "${1:?No theme declared}"
}
set_mouse() {
gsettings set org.cinnamon.desktop.interface cursor-theme "${1:?No theme declared}"
}
set_controls() {
gsettings set org.cinnamon.desktop.interface gtk-theme "${1:?No theme declared}"
}
set_wallpaper() {
if [[ ! -r "${1:?No wallpaper declared}" ]]; then
printf -- '%s\n' "${prog_name} ERROR: Could not find or read '${1}'" >&2
exit 1
fi
gsettings set org.cinnamon.desktop.background picture-uri "file://${1}"
}
set_wmborders() {
gsettings set org.cinnamon.desktop.wm.preferences theme "${1:?No theme declared}"
}
set_dark_theme_vars() {
var_is_set "${dark_wmborders}" && wmborders="${dark_wmborders}"
var_is_set "${dark_icons}" && icons="${dark_icons}"
var_is_set "${dark_controls}" && controls="${dark_controls}"
var_is_set "${dark_mouse}" && mouse="${dark_mouse}"
var_is_set "${dark_desktop}" && desktop="${dark_desktop}"
}
set_light_theme_vars() {
var_is_set "${light_wmborders}" && wmborders="${light_wmborders}"
var_is_set "${light_icons}" && icons="${light_icons}"
var_is_set "${light_controls}" && controls="${light_controls}"
var_is_set "${light_mouse}" && mouse="${light_mouse}"
var_is_set "${light_desktop}" && desktop="${light_desktop}"
}
set_theme_vars_by_time() {
if (( current_time > lights_off ))||(( current_time < lights_on )); then
set_dark_theme_vars
else
set_light_theme_vars
fi
}
set_theme() {
[[ "$(get_wmborders)" != "${wmborders}" ]] && set_wmborders "${wmborders}"
[[ "$(get_icons)" != "${icons}" ]] && set_icons "${icons}"
[[ "$(get_controls)" != "${controls}" ]] && set_controls "${controls}"
[[ "$(get_mouse)" != "${mouse}" ]] && set_mouse "${mouse}"
[[ "$(get_desktop)" != "${desktop}" ]] && set_desktop "${desktop}"
if var_is_set "${wallpaper}"; then
[[ "$(get_wallpaper)" != "${wallpaper}" ]] && set_wallpaper "${wallpaper}"
fi
}
set_theme_light() {
set_light_theme_vars
if [ -f "${app_filename}" ]; then
sed 's/^Icon.*/Icon=sunny/' "${app_filename}" > /tmp/.lightswitch.light
cp /tmp/.lightswitch.light "${app_filename}"
fi
set_theme
}
set_theme_dark() {
set_dark_theme_vars
if [ -f "${app_filename}" ]; then
if dpkg -l | grep -q -- "papirus-icon-theme"; then
sed 's|^Icon.*|Icon=palemoon|' "${app_filename}" > /tmp/.lightswitch.dark
else
sed 's/^Icon.*/Icon=night-light-symbolic/' "${app_filename}" > /tmp/.lightswitch.dark
fi
cp /tmp/.lightswitch.dark "${app_filename}"
fi
set_theme
}
set_theme_auto() {
if (( current_time > lights_off ))||(( current_time < lights_on )); then
set_theme_dark
else
set_theme_light
fi
}
flip_theme() {
found_theme=$(get_controls | tr -d "\'")
if [[ "${found_theme}" = "${dark_controls}" ]]; then
set_theme_light
exit 0
elif [[ "${found_theme}" = "${light_controls}" ]]; then
set_theme_dark
exit 0
else
set_theme_auto
fi
}
setup_papirus() {
add-apt-repository ppa:papirus/papirus
apt-get update
apt-get install papirus-icon-theme
}
validate_config() {
local conf_file conf_errcount
conf_file="${1:?No config file defined}"
[[ -r "${conf_file}" ]] || {
printf -- '%s\n' "Could not read ${conf_file}" >&2
exit 1
}
[[ -s "${conf_file}" ]] || {
printf -- '%s\n' "${conf_file} appears to be empty" >&2
exit 1
}
# TO-DO: Update the filter to cater for configs that don't use dark_ or light_ vars
conf_errcount="$(grep -Evc '^dark_.*=|^light_.*=|^lights_.*=|^$|^#' "${conf_file}")"
(( conf_errcount > 0 )) && {
printf -- '%s\n' "Invalid config found in ${conf_file}" >&2
exit 1
}
}
write_var() {
printf -- '%s="%s"\n' "${1:?No key supplied}" "${2:?No value supplied}"
}
# TO-DO: plug this into getopts
write_config() {
local output_file
output_file="${1:?No output path defined}"
{
printf -- '%s\n' "# Set our times to switch between light and dark themes (24hr time)"
write_var "lights_on" "${lights_on:-0900}"
write_var "lights_off" "${lights_off:-2000}"
printf -- '%s\n' "" "# Define our theme settings"
var_is_set "${wmborders}" && write_var "wmborders" "${wmborders}"
var_is_set "${icons}" && write_var "icons" "${icons}"
var_is_set "${controls}" && write_var "controls" "${controls}"
var_is_set "${mouse}" && write_var "mouse" "${mouse}"
var_is_set "${desktop}" && write_var "desktop" "${desktop}"
var_is_set "${wallpaper}" && write_var "desktop" "${wallpaper}"
} > "${output_file}"
}
usage() {
printf '%s\n' "${prog_name} - Switch themes between light and dark themes" \
"Also includes ability to set individual theme elements" ""
printf '\t%s\n' "Options:" \
" -b set window borders to the given theme" \
" -c clone https://github.com/linuxmint/cinnamon-spices-themes into ~/.themes/" \
" -C read supplied config file" \
" -d set desktop panels to the given theme" \
" -f flip between light and dark themes" \
" -g get papirus icon themes" \
" -h usage" \
" -i set icons to the given theme" \
" -l list current theme, set elements and available themes" \
" -m set mouse pointer to the given theme" \
" -p add a shortcut for the flip mode to the panel" \
" -s create desktop shortcuts for light and dark modes" \
" -t set theme. Can be one of the following arguments:" \
" a|auto - selects the '${prog_name}' light/dark theme based on the time" \
" dark|Dark - the '${prog_name}' dark theme" \
" light|Light - the '${prog_name}' light theme" \
" [anything else] - will attempt to load a gtk theme. See '-l'." \
" -w set wallpaper to the given file"
exit 0
}
main() {
# If no args are given, assume auto mode
if (( "${#}" == 0 )); then
set_theme_auto
exit 0
fi
# Otherwise, we process our args using getopts
while getopts ":b:cC:d:fghi:lm:pst:w:" optFlags; do
case "${optFlags}" in
(b) wmborders="${OPTARG}" ;;
(c) get_cinnamon_spice ;;
(C)
read_config=true
config_file="${OPTARG}"
;;
(d) desktop="${OPTARG}" ;;
(f) flip_theme ;;
(g)
sudo -E bash -c "$(declare -f setup_papirus); setup_papirus"
sudo -k
;;
(h) usage ;;
(i) icons="${OPTARG}" ;;
(l) list_themes ;;
(m) mouse="${OPTARG}" ;;
(p)
sudo -E bash -c "$(declare -f create_panel_shortcut); create_panel_shortcut"
sudo -k
add_panel_shortcut
flip_theme
;;
(s) create_desktop_shortcuts ;;
(t)
case "${OPTARG}" in
(a|auto) set_theme_auto ;;
([dD]ark) set_theme_dark ;;
([lL]ight) set_theme_light ;;
(*)
wmborders="${OPTARG}"
icons="${OPTARG}"
controls="${OPTARG}"
mouse="${OPTARG}"
desktop="${OPTARG}"
;;
esac
;;
(w) wallpaper="${OPTARG}" ;;
(\?)
printf -- '%s\n' "${prog_name} ERROR: Invalid option: '-${OPTARG}'" >&2
exit 1
(:)
printf -- '%s\n' "${prog_name} ERROR: Option '-${OPTARG}' requires an argument" >&2
exit 1
;;
esac
done
# If we have a config file, read it now to ensure that its settings take precedence
if [[ "${read_config}" = "true" ]]; then
validate_config "${config_file}"
# shellcheck disable=SC1090
source "${config_file}"
# If the config file contains dark_* and/or light_* vars
# This function will map them relative to the current time
set_theme_vars_by_time
# Force these variables into decimal mode to prevent octal interpretation
lights_on=$(( 10#${lights_on} ))
lights_off=$(( 10#${lights_off} ))
fi
set_theme
}
main "${@}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment