[download]

local/bin/papirus-folders

   1 #!/usr/bin/env bash
   2 # This script allows changing the color of folders in Papirus icon theme
   3 #
   4 # @author: Sergei Eremenko (https://github.com/SmartFinn)
   5 # @license: MIT license (MIT)
   6 # @link: https://github.com/PapirusDevelopmentTeam/papirus-folders
   7 
   8 if test -z "$BASH_VERSION"; then
   9 	printf "Error: this script only works in bash.\n" >&2
  10 	exit 1
  11 fi
  12 
  13 if (( BASH_VERSINFO[0] * 10 + BASH_VERSINFO[1] < 40 )); then
  14 	printf "Error: this script requires bash version >= 4.0\n" >&2
  15 	exit 1
  16 fi
  17 
  18 # set -x  # Uncomment to debug this shell script
  19 set -o errexit \
  20 	-o noclobber \
  21 	-o pipefail
  22 
  23 readonly THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
  24 readonly PROGNAME="$(basename "${BASH_SOURCE[0]}")"
  25 readonly VERSION="1.7.0"
  26 readonly -a ARGS=("$@")
  27 
  28 msg() {
  29 	printf "%s: %b\n" "$PROGNAME" "$*"
  30 }
  31 
  32 verbose() {
  33 	[ -t 4 ] || return 0
  34 	msg "$@" >&4
  35 }
  36 
  37 err() {
  38 	msg "Error:" "$*" >&2
  39 }
  40 
  41 _exit() {
  42 	msg "$*" "Exiting ..."
  43 	exit 0
  44 }
  45 
  46 fatal() {
  47 	err "$*"
  48 	exit 1
  49 }
  50 
  51 usage() {
  52 	cat <<- EOF
  53 	USAGE
  54 	  $ $PROGNAME [options] -t <theme> {-C --color} <color>
  55 	  $ $PROGNAME [options] -t <theme> {-D --default}
  56 	  $ $PROGNAME [options] -t <theme> {-R --restore}
  57 
  58 	OPERATIONS
  59 	  -C --color <color>  change color of folders
  60 	  -D --default        back to the default color
  61 	  -R --restore        restore the last used color from the config file
  62 
  63 	OPTIONS
  64 	  -l --list           show available colors
  65 	  -o --once           do not save the changes to the config file
  66 	  -t --theme <theme>  make changes to the specified theme (Default: Papirus)
  67 	  -u --update-caches  update icon caches for Papirus and siblings
  68 	  -V --version        print $PROGNAME version and exit
  69 	  -v --verbose        be verbose
  70 	  -h --help           show this help
  71 	EOF
  72 
  73 	exit "${1:-0}"
  74 }
  75 
  76 _is_root_user() {
  77 	if [ "$(id -u)" -eq 0 ]; then
  78 		return 0
  79 	fi
  80 
  81 	return 1
  82 }
  83 
  84 _is_user_dir() {
  85 	[ -n "$USER_HOME" ] || return 1
  86 
  87 	# if $THEME_DIR is placed in home dir
  88 	if [ -z "${THEME_DIR##"$USER_HOME"/*}" ]; then
  89 		return 0
  90 	fi
  91 
  92 	return 1
  93 }
  94 
  95 _is_writable() {
  96 	if [ -w "$THEME_DIR/48x48/places/folder.svg" ]; then
  97 		return 0
  98 	fi
  99 
 100 	return 1
 101 }
 102 
 103 _is_valid_color() {
 104 	local color="$1"
 105 
 106 	eval "$(declare_colors)"
 107 
 108 	for i in "${colors[@]}"; do
 109 		[ "$i" == "$color" ] || continue
 110 		return 0
 111 	done
 112 
 113 	return 1
 114 }
 115 
 116 declare_colors() {
 117 	local color=''
 118 	local -a colors=()
 119 	local -a valid_colors=("black" "blue" "bluegrey" "breeze" "brown"
 120 		"cyan" "deeporange" "green" "grey" "indigo" "magenta" "nordic"
 121 		"orange" "palebrown" "paleorange" "pink" "purple" "red" "teal"
 122 		"violet" "white" "yaru" "yellow")
 123 
 124 	for color in "${valid_colors[@]}"; do
 125 		if [ -e "$THEME_DIR/48x48/places/folder-$color.svg" ]; then
 126 			colors=( "${colors[@]}" "$color" )
 127 		fi
 128 	done
 129 
 130 	# return array of colors
 131 	declare -p colors
 132 }
 133 
 134 declare_current_color() {
 135 	local icon_file icon_name current_color=''
 136 
 137 	icon_file=$(readlink -f "$THEME_DIR/48x48/places/folder.svg")
 138 	icon_name=$(basename "$icon_file" .svg)
 139 	current_color="${icon_name##*-}"
 140 
 141 	declare -p current_color
 142 }
 143 
 144 get_theme_dir() {
 145 	local data_dir icons_dir
 146 	local -a data_dirs=()
 147 	local -a icons_dirs=(
 148 		"$USER_HOME/.icons"
 149 		"${XDG_DATA_HOME:-$USER_HOME/.local/share}/icons"
 150 	)
 151 
 152 	# Get data directories from XDG_DATA_DIRS variable and
 153 	# convert colon-separated list into bash array
 154 	IFS=: read -ra data_dirs <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
 155 
 156 	for data_dir in "${data_dirs[@]}"; do
 157 		[ -d "$data_dir/icons" ] || continue
 158 		icons_dirs=( "${icons_dirs[@]}" "${data_dir%/}/icons" )
 159 	done
 160 
 161 	for icons_dir in "${icons_dirs[@]}"; do
 162 		[ -f "$icons_dir/$THEME_NAME/index.theme" ] || continue
 163 		printf '%s' "$icons_dir/$THEME_NAME"
 164 		verbose "'$THEME_NAME' is found in '$icons_dir'."
 165 		return 0
 166 	done
 167 
 168 	return 1
 169 }
 170 
 171 get_real_user() {
 172 	# return name of the user that runs the script
 173 	local user=''
 174 
 175 	if [ -n "$PKEXEC_UID" ]; then
 176 		user="$(id -nu "$PKEXEC_UID")"
 177 	elif [ -n "$SUDO_USER" ]; then
 178 		user="$SUDO_USER"
 179 	else
 180 		user="$(id -nu)"
 181 	fi
 182 
 183 	printf '%s' "$user"
 184 }
 185 
 186 get_user_home() {
 187 	local user="$1"
 188 
 189 	getent passwd "$user" | awk -F: '{print $(NF-1)}'
 190 }
 191 
 192 config() {
 193 	# usage: config [{-n --new}] {-s --set} key=value... | {-g --get} key...
 194 	local config_dir
 195 	local config_file
 196 
 197 	if _is_user_dir; then
 198 		config_dir="${XDG_CONFIG_HOME:-$USER_HOME/.config}/$PROGNAME"
 199 	else
 200 		config_dir="/var/lib/$PROGNAME"
 201 	fi
 202 
 203 	config_file="$config_dir/keep"
 204 
 205 	while (( "$#" )); do
 206 		case "$1" in
 207 			-g|--get) shift;
 208 				[ -f "$config_file" ] || return 1
 209 
 210 				for key; do
 211 					[ -n "$key" ] || continue
 212 					awk -F= -v key="$key" '
 213 					$1 == key {
 214 						print $2
 215 						exit
 216 					}
 217 					' "$config_file"
 218 				done
 219 
 220 				break
 221 				;;
 222 			-n|--new) shift;
 223 				rm -f "$config_file"
 224 				;;
 225 			-e|--exists) shift;
 226 				# return 1 if test config_file not exist or empty
 227 				if [ -f "$config_file" ] && [ -s "$config_file" ]; then
 228 					return 0
 229 				else
 230 					return 1
 231 				fi
 232 				;;
 233 			-s|--set) shift;
 234 				[ "$ONCE" -eq "1" ] && break
 235 				[ -d "$config_dir"  ] || mkdir -p "$config_dir"
 236 				[ -f "$config_file" ] || touch "$config_file"
 237 
 238 				verbose "Saving params to '$config_file' ..."
 239 				cat >> "$config_file" <<- EOF
 240 				$(for key_value; do echo "$key_value"; done)
 241 				EOF
 242 
 243 				break
 244 				;;
 245 			*)
 246 				err "illegal option -- '$1'"
 247 				return 1
 248 		esac
 249 	done
 250 
 251 	return 0
 252 }
 253 
 254 change_color() {
 255 	local color="${1:?${FUNCNAME[-1]}: color is not set}"
 256 	local size prefix file_path file_name symlink_path
 257 	local -a sizes=(22x22 24x24 32x32 48x48 64x64)
 258 	local -a prefixes=("folder-$color" "user-$color")
 259 
 260 	for size in "${sizes[@]}"; do
 261 		for prefix in "${prefixes[@]}"; do
 262 			for file_path in "$THEME_DIR/$size/places/$prefix"{-*,}.svg; do
 263 				[ -f "$file_path" ] || continue  # is a file
 264 				[ -L "$file_path" ] && continue  # is not a symlink
 265 
 266 				file_name="${file_path##*/}"
 267 				symlink_path="${file_path/-$color/}"  # remove color suffix
 268 
 269 				ln -sf "$file_name" "$symlink_path" || {
 270 					fatal "Fail to create '$symlink_path' symlink"
 271 				}
 272 			done
 273 		done
 274 	done
 275 }
 276 
 277 list_colors() {
 278 	local color='' prefix=''
 279 
 280 	eval "$(declare_colors)"
 281 	eval "$(declare_current_color)"
 282 
 283 	for color in "${colors[@]}"; do
 284 		if [ "$current_color" == "$color" ]; then
 285 			prefix='>'
 286 		else
 287 			prefix=''
 288 		fi
 289 
 290 		printf '%2s %s\n' "$prefix" "$color"
 291 	done
 292 }
 293 
 294 do_change_color() {
 295 	_is_valid_color "$SELECTED_COLOR" || {
 296 		fatal "Unable to find '$SELECTED_COLOR' color in '$THEME_NAME'"
 297 	}
 298 
 299 	verify_privileges
 300 
 301 	msg "Changing color of folders to '$SELECTED_COLOR' for '$THEME_NAME' ..."
 302 	change_color "$SELECTED_COLOR"
 303 	config --new --set "theme=$THEME_NAME" "color=$SELECTED_COLOR"
 304 	update_icon_cache
 305 }
 306 
 307 do_revert_default() {
 308 	verify_privileges
 309 
 310 	msg "Restoring default folder color for '$THEME_NAME' ..."
 311 	change_color "${DEFAULT_COLORS[$THEME_NAME]:-blue}"
 312 	config --new
 313 	update_icon_cache
 314 }
 315 
 316 do_restore_color() {
 317 	local saved_color=''
 318 
 319 	if config --exists; then
 320 		THEME_NAME="$(config --get theme)"
 321 		saved_color="$(config --get color)"
 322 	else
 323 		_exit "Unable to find config file."
 324 	fi
 325 
 326 	THEME_DIR="$(get_theme_dir)" || {
 327 		_exit "Unable to find '$THEME_NAME' icon theme."
 328 	}
 329 
 330 	_is_valid_color "$saved_color" || {
 331 		_exit "Unable to find '$saved_color' color in '$THEME_NAME'."
 332 	}
 333 
 334 	verify_privileges
 335 
 336 	change_color "$saved_color"
 337 	msg "'$saved_color' color of the folders has been restored."
 338 }
 339 
 340 delete_icon_caches() {
 341 	local icon_cache real_user='' real_home=''
 342 
 343 	real_user="$(get_real_user)"
 344 	real_home="$(get_user_home "$real_user")"
 345 
 346 	declare -a icon_caches=(
 347 		# KDE 5 icon caches
 348 		"$real_home/.cache/icon-cache.kcache"
 349 		"/var/tmp/kdecache-$real_user/icon-cache.kcache"
 350 	)
 351 
 352 	verbose "Deleting icon caches ..."
 353 	for icon_cache in "${icon_caches[@]}"; do
 354 		[ -e "$icon_cache" ] || continue
 355 		rm -f "$icon_cache"
 356 	done
 357 }
 358 
 359 update_icon_cache() {
 360 	delete_icon_caches
 361 
 362 	verbose "Rebuilding icon cache for '$THEME_NAME' ..."
 363 	gtk-update-icon-cache -qf "$THEME_DIR" || true
 364 }
 365 
 366 update_icon_caches() {
 367 	local theme=''
 368 
 369 	delete_icon_caches
 370 
 371 	for theme in "${!DEFAULT_COLORS[@]}"; do
 372 		[ -f "$THEME_DIR/../$theme/index.theme" ] || continue
 373 		verbose "Rebuilding icon cache for '$theme' ..."
 374 		gtk-update-icon-cache -qf "$THEME_DIR/../$theme" || true
 375 	done
 376 }
 377 
 378 verify_privileges() {
 379 	_is_root_user && return 0
 380 	_is_user_dir  && return 0
 381 	_is_writable  && return 0
 382 
 383 	verbose "This operation requires root privileges."
 384 
 385 	if command -v sudo > /dev/null; then
 386 		exec sudo USER_HOME="$USER_HOME" XDG_DATA_DIRS="$XDG_DATA_DIRS" \
 387 			"$THIS_SCRIPT" "${ARGS[@]}"
 388 	else
 389 		fatal "You need to be root to run this command."
 390 	fi
 391 }
 392 
 393 parse_args() {
 394 	local arg='' opt=''
 395 	local -a args=()
 396 
 397 	# Show help if no argument is passed
 398 	if [ -z "$1" ]; then
 399 		usage 2
 400 	fi
 401 
 402 	# Translate --gnu-long-options to -g (short options)
 403 	for arg; do
 404 		case "$arg" in
 405 			--help)           args+=( -h ) ;;
 406 			--list)           args+=( -l ) ;;
 407 			--once)           args+=( -o ) ;;
 408 			--theme)          args+=( -t ) ;;
 409 			--update-caches)  args+=( -u ) ;;
 410 			--verbose)        args+=( -v ) ;;
 411 			--color|--colour) args+=( -C ) ;;
 412 			--default)        args+=( -D ) ;;
 413 			--restore)        args+=( -R ) ;;
 414 			--version)        args+=( -V ) ;;
 415 			--[0-9a-Z]*)
 416 				err "illegal option -- '$arg'"
 417 				usage 2
 418 				;;
 419 			*) args+=("$arg")
 420 		esac
 421 	done
 422 
 423 	# Reset the positional parameters to the short options
 424 	set -- "${args[@]}"
 425 
 426 	while getopts ":C:DRlot:uvVh" opt; do
 427 		case "$opt" in
 428 			C ) OPERATIONS+=("change-color")
 429 				SELECTED_COLOR="$OPTARG"
 430 				;;
 431 			D ) OPERATIONS+=("revert-default") ;;
 432 			R ) OPERATIONS+=("restore-color") ;;
 433 			l ) OPERATIONS+=("list-colors") ;;
 434 			o ) ONCE=1 ;;
 435 			t ) THEME_NAME="$OPTARG" ;;
 436 			u ) OPERATIONS+=("update-icon-caches") ;;
 437 			v ) VERBOSE=1 ;;
 438 			V ) printf "%s %s\n" "$PROGNAME" "$VERSION"
 439 				exit 0
 440 				;;
 441 			h ) usage 0 ;;
 442 			: ) err "option requires an argument -- '-$OPTARG'"
 443 				usage 2
 444 				;;
 445 			\?) err "illegal option -- '-$OPTARG'"
 446 				usage 2
 447 				;;
 448 		esac
 449 	done
 450 
 451 	shift $((OPTIND-1))
 452 
 453 	# Return an error if any positional parameters are found
 454 	if [ -n "$1" ]; then
 455 		err "illegal parameter -- '$1'"
 456 		usage 2
 457 	fi
 458 }
 459 
 460 main() {
 461 	# default values of options
 462 	declare THEME_NAME="${THEME_NAME:-Papirus}"
 463 	declare -i VERBOSE="${VERBOSE:-0}"
 464 	declare -i ONCE="${ONCE:-0}"
 465 	declare -A DEFAULT_COLORS=(
 466 		['ePapirus']='blue'
 467 		['Papirus']='blue'
 468 		['Papirus-Dark']='blue'
 469 	)
 470 
 471 	declare SELECTED_COLOR=''
 472 	declare -a OPERATIONS=()
 473 
 474 	parse_args "${ARGS[@]}"
 475 
 476 	if [ "$VERBOSE" -eq "1" ]; then
 477 		# open a file descriptor for verbose messages
 478 		exec 4>&1
 479 		# close the file descriptor before exiting
 480 		trap 'exec 4>&-' EXIT HUP INT TERM
 481 	fi
 482 
 483 	# set USER_HOME variable instead HOME to prevent changing user's icons
 484 	# when running with sudo
 485 	[ -n "$USER_HOME" ] || USER_HOME="$(get_user_home "$(id -nu)")"
 486 
 487 	if [ -f "$THEME_NAME/index.theme" ]; then
 488 		# THEME_NAME is a path to an icon theme
 489 		THEME_DIR="$(readlink -f "$THEME_NAME")"
 490 		THEME_NAME="$(basename "$THEME_DIR")"
 491 		verbose "The path to '$THEME_DIR' theme is specified."
 492 	else
 493 		THEME_DIR="$(get_theme_dir)" || {
 494 			fatal "Fail to find '$THEME_NAME' icon theme."
 495 		}
 496 	fi
 497 
 498 	for operation in "${OPERATIONS[@]}"; do
 499 		case "$operation" in
 500 			change-color)
 501 				do_change_color
 502 				;;
 503 			revert-default)
 504 				do_revert_default
 505 				;;
 506 			restore-color)
 507 				do_restore_color
 508 				;;
 509 			list-colors)
 510 				cat <<- EOF
 511 				List of available colors:
 512 
 513 				$(list_colors)
 514 
 515 				EOF
 516 				;;
 517 			update-icon-caches)
 518 				verify_privileges
 519 				update_icon_caches
 520 				;;
 521 		esac
 522 	done
 523 
 524 	verbose "Done!"
 525 
 526 	exit 0
 527 }
 528 
 529 main
 530 
 531 exit 1