-
Notifications
You must be signed in to change notification settings - Fork 0
/
dark-toggle
executable file
·346 lines (279 loc) · 9.83 KB
/
dark-toggle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#!/bin/sh
# A simple POSIX compliant script to toggle between dark and light variant
# of a theme for GNOME/GTK based applications.
# Copyright (C) 2021 Rifaz Nahiyan
# This code is licensed under the MIT License.
# View the license in its entirety at: https://opensource.org/licenses/MIT
# Constants
SCRIPTNAME="${0##*/}"
SCRIPTNAME="${SCRIPTNAME%.sh}"
SYS_CONFIG="/etc/dark-toggle/config"
USER_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/dark-toggle/config"
CURRENT_THEME=
# Options from CLI or config
change_icons=false
change_shell=false
exec_hooks=false
# Globals
new_theme=
warnings=
# Helper functions
get_current_theme () {
gsettings get org.gnome.desktop.interface gtk-theme | tr --delete \'
}
set_theme () {
gsettings set org.gnome.desktop.interface gtk-theme "$1"
# Unfortunately, gsettings always reports exit status 0
}
set_shell_theme () {
local theme_name="$1"
local schema="org.gnome.shell.extensions.user-theme"
local ext_name="user-theme@gnome-shell-extensions.gcampax.github.com"
local schema_dir="$HOME/.local/share/gnome-shell/extensions/$ext_name/schemas"
# Get current shell theme name
local current_shell_theme="$(gsettings get "$schema" name 2>/dev/null \
|| gsettings --schemadir "$schema_dir" get "$schema" name 2>/dev/null)"
# Don't bother changing the theme if already in the same theme
if [ "$current_shell_theme" = "$theme_name" ]; then
return 0
else
gsettings set "$schema" name "$theme_name" 2>/dev/null \
|| gsettings --schemadir "$schema_dir" set "$schema" name "$theme_name" 2>/dev/null
fi
}
# Sanity checks
die () {
# Display a formatted message, then exit with error
printf "%s: %s\n" "${0##*/}" "${*}" >&2
exit 1
}
print_warnings () {
IFS=:
printf "%s: errors while running the script.\n" "${SCRIPTNAME}" >&2
printf " - %s\n" $* >&2
unset IFS
}
deps_check () {
# Check dependencies. Currently, all deps are considered required.
local deps="gsettings notify-send sed tr"
local missing=""
for dep in ${deps}; do
command -v "${dep}" >/dev/null || missing="${missing} ${dep}"
done
if [ -n "${missing}" ]
then
die "Missing necessary dependencies: ${missing}"
fi
}
theme_check () {
# Does the theme exist (installed) in the system?
local user_theme="$HOME/.themes/$1"
local system_theme="/usr/share/themes/$1"
[ -f "$user_theme/gtk-3.0/gtk.css" ] || [ -f "$system_theme/gtk-3.0/gtk.css" ]
}
# Configuration Parsers
parse_options () {
# Takes a tokenized form of a config file's contents to parse the config
# Returns alternate theme found from "mappings" section of config file
local shift_by=1
while [ $# -gt 0 ]
do
case $1 in
# Options
change-icons) change_icons=true ;;
change-shell) change_shell=true ;;
exec-hooks) exec_hooks=true ;;
# Mappings
mappings) [ $2 = '{' ] && shift_by=2 && shift 2 ;;
'}') shift_by=1 ;;
esac
# Add to $mappings to parse later on
if [ $shift_by -eq 2 ]; then
mappings="$mappings""$1 $2 "
fi
shift $shift_by
done
}
parse_maps () {
# Accepts the $mappings variable from parse_config()
# Returns name of the alt theme and variant if a mapping exists
local alt_theme= variant=
while [ $# -gt 0 ]; do
[ "$1" = "$CURRENT_THEME" ] && alt_theme="$2" && variant="dark"
[ "$2" = "$CURRENT_THEME" ] && alt_theme="$1" && variant="light"
shift 2
done
[ -n "$alt_theme" ] && printf "$alt_theme $variant"
}
# Main meat of the script
guess_theme_name () {
# Guess the alternate variant of the current theme based on generalized
# theme naming conventions (i.e. by adding/removing dark/light suffix)
case $CURRENT_THEME in
*[dD]ark|*[dD]ark-[cC]ompact)
# Example: from Arc-Dark to Arc
base_name="${CURRENT_THEME%?[Dd]ark*}"
# Get the [cC]ompact part so we don't have to guess if its upper or lower case
_uncompacted="${CURRENT_THEME%?[cC]ompact*}"
_compact="${CURRENT_THEME#$_uncompacted}"
for lightcase in '' light -light Light -Light; do
theme="$base_name$lightcase$_compact"
if theme_check "$theme"; then
new_theme="$theme"
variant_found=light
break;
fi
done
;;
*)
# Example: From Materia-light-compact to Materia
base_name="${CURRENT_THEME%[cC]ompact*}"
base_name="${base_name%?[lL]ight*}"
# Get the [cC]ompact part so we don't have to guess if its upper or lower case
_uncompacted="${CURRENT_THEME%?[cC]ompact*}"
_compact="${CURRENT_THEME#$_uncompacted}"
for darkcase in dark -dark Dark -Dark; do
theme="$base_name$darkcase$_compact"
if theme_check "$theme"; then
new_theme="$theme"
variant_found=dark
break;
fi
done
;;
esac
[ -n "$new_theme" ] && printf "$new_theme $variant_found"
}
change_icon_theme () {
# change to the icon theme that matches with the gtk_theme name the most
local gtk_theme="$1"
local longest_match=0
local icon_name=
local usr_icons_dir="$HOME/.icons"
local sys_icons_dir="/usr/share/icons"
for icon_dir in $sys_icons_dir/* $usr_icons_dir/*; do
# icon file exists?
[ ! -f $icon_dir/index.theme ] && continue
icon=${icon_dir##*/}
# expr ensures that match is at least $gtk_theme's length
match_len="$(expr $gtk_theme : $icon)"
[ $match_len -gt $longest_match ] && longest_match=$match_len && icon_name=$icon
done
[ -n "$icon_name" ] && gsettings set org.gnome.desktop.interface icon-theme "$icon_name"
}
change_shell_theme () {
# Change to the shell theme whose name matches the most with $gtk_theme
local gtk_theme="$1"
local default_shell_theme_path="/usr/share/gnome-shell/theme"
local longest_match=0 theme_chosen=
local extension_enabled=
#
# Some sanity checks
#
# Is this a GNOME based desktop?
case "$XDG_CURRENT_DESKTOP" in
*GNOME*) : ;; # OK
*)
warnings="$warnings"'Not changing shell theme as this is not a GNOME-based desktop.'
return 1; ;;
esac
# Is the user-theme extension enabled?
for cmd in "gsettings get org.gnome.shell enabled-extensions" "gnome-extensions list --enabled"
do
command -v "${cmd%% *}" >/dev/null || continue
case $($cmd) in
*user-theme@gnome-shell-extensions.gcampax.github.com*) extension_enabled=true ;;
*) : ;;
esac
done
if [ "$extension_enabled" != "true" ]; then
warnings="$warnings"'GNOME shell theme not changed as the extension "GNOME User Themes" is not enabled.'
return 1
fi
#
# Finding and setting the default Gnome Shell Theme (gst)
#
# Here we're presented with a special case.
# First, you change to the "Default" shell theme by passing an empty '' to gsettings
# Second, the default shell theme is not always "Adwaita", as distros can change that.
# So first, we try to find _what_ the default shell theme is. Then if $gtk_theme
# matches the derived default shell theme, we set the shell theme to ''
# Look for a folder in /usr/share/gnome-shell/theme
# If none found, Adwaita is the default gst
# If found, that is the default gst
for theme in /usr/share/gnome-shell/theme/*; do
# break at the first directory found
[ -d "$theme" ] && theme="${theme##*/}" && break
# Unset if current item is not a directory
unset theme
done
default_shell_theme="${theme:-Adwaita}"
# Special case for default shell theme
case $gtk_theme in "$default_shell_theme"*) set_shell_theme ''; return 0; ;; esac
# Try to make a guess what the _shell_ theme for the current _gtk_ theme should be
for theme in /usr/share/themes/*; do
[ ! -d "$theme/gnome-shell" ] && continue
theme="${theme##*/}"
local match="$(expr "$gtk_theme.*" : "$theme")"
[ "$match" -gt "$longest_match" ] && longest_match="$match" && theme_chosen="$theme"
done
[ -n "$theme_chosen" ] && set_shell_theme "$theme_chosen"
}
main () {
deps_check
local mappings=
CURRENT_THEME="$(get_current_theme)"
#
# Parsing the config file for options and theme mappings
#
# Tokenize the config files
[ -f "$USER_CONFIG" ] && user_conf_tokens="$(sed 's/#.*//' "$USER_CONFIG" | tr '[[:space:]]' ' ')"
[ -f "$SYS_CONFIG" ] && sys_conf_tokens="$(sed 's/#.*//' "$SYS_CONFIG" | tr '[[:space:]]' ' ')"
# Parse options from the user's tokenized config. Also, importantly, populates $mappings
set -f; parse_options $user_conf_tokens
# Read user defined mappings by parsing $mappings
read theme_usr variant_usr <<-EOF
$(parse_maps $mappings)
EOF
variant_usr="${variant_usr:+$variant_usr (custom)}"
# Parse options from the system's tokenized config. Also populates $mappings
parse_options $sys_conf_tokens; set +f
# Read system/distro/release defined mappings by parsing $mappings
read theme_sys variant_sys <<-EOF
$(parse_maps $mappings)
EOF
# If a mapping was found, but the theme is not installed, raise an appropriate warning
if [ -n "$theme_usr" ] && ! theme_check "$theme_usr"; then
theme_usr= variant_usr=
warn="The user-defined variant of the current theme is not installed in the system."
warnings="$warnings""$warn:"
fi
if [ -n "$theme_sys" ] && ! theme_check "$theme_sys"; then
theme_sys= variant_sys=
warn="The alternate variant of the current theme is not installed in the system."
warnings="$warnings""$warn:"
fi
# Prefer user defined theme over system defined. Null is also possible
new_theme="${theme_usr:-$theme_sys}"
[ -n "$new_theme" ] && variant_found="${variant_usr:-$variant_sys}"
#
# Most themes will be derived this way.
# If a mapping from either of the config was not found,
# make a guess of the alternate variant from the current theme's name
#
if [ -z "$new_theme" ]; then
read new_theme variant_found <<-EOF
$(guess_theme_name)
EOF
fi
# Quit if no themes found at this stage
[ -z "$new_theme" ] && die "No valid alternate variant found for current theme: $CURRENT_THEME"
# Set the new gtk/+shell/+icon theme and wrap up
set_theme "$new_theme"
[ "$change_icons" = "true" ] && change_icon_theme "$new_theme"
[ "$change_shell" = "true" ] && change_shell_theme "$new_theme"
[ -n "$warnings" ] && print_warnings "$warnings"
notify_msg="Theme switched to ${variant_found} variant."
notify-send -t 5000 "${SCRIPTNAME}" "$notify_msg"
}
main "${@}"