2026-05-17 06:56:33 +00:00
#!/usr/bin/env bash
2026-05-17 15:46:43 +00:00
# SPDX-FileCopyrightText: 2026 Marco Allegretti <mightymarco4@gmail.com>
# SPDX-License-Identifier: EUPL-1.2
2026-05-17 06:56:33 +00:00
set -euo pipefail
repo_dir = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) /.. " && pwd ) "
cd " $repo_dir "
theme_dir = "icons/org.shift.icons"
required_icons = " $( mktemp) "
missing_icons = " $( mktemp) "
broken_icons = " $( mktemp) "
plasma_files = " $( mktemp) "
trap 'rm -f "$required_icons" "$missing_icons" "$broken_icons" "$plasma_files"' EXIT
icon_name_prefix_re = '^(accessories|activities|applications?|application-|appointment|archive|arrow-|audio|battery|bluetooth|bookmarks|call|camera-|chronometer|color|configure|computer|device-|dialog-|document|drive|edit-|emblem-|entry|favorites|flashlight|folder|format|games|games-|get-hot-new-stuff|go-|help|high-brightness|input-|internet|kcm|keyboard|klipper|krunner|layer|list-|low-brightness|mail|media-|mobile-|network-|notifications|object|office|org\.kde\.|osd-|phone|plasma|preferences-|printer|process|redshift|rotation-|search|security|settings-|software|speedometer|starred|start-here|system-|tablet|tools|touchpad|trash-|unknown|user|utilities|video|view|view-|virtual-|weather|widget-|window|zoom)'
fail( ) {
printf '%s\n' " $1 " >& 2
exit 1
}
require_line( ) {
local file = " $1 "
local pattern = " $2 "
local message = " $3 "
grep -Eq " $pattern " " $file " || fail " $message "
}
icon_exists( ) {
local icon_name = " $1 "
[ [ -e " $theme_dir /preferences/scalable/ $icon_name .svg " ] ] && return 0
[ [ -e " $theme_dir /actions/symbolic/ $icon_name .svg " ] ] && return 0
[ [ -e " $theme_dir /places/symbolic/ $icon_name .svg " ] ] && return 0
[ [ -e " $theme_dir /apps/scalable/ $icon_name .svg " ] ] && return 0
return 1
}
require_directory_order( ) {
local directories
directories = " $( awk -F= '$1 == "Directories" { print $2 }' " $theme_dir /index.theme " ) "
[ [ " $directories " = = preferences/scalable,*actions/symbolic* ] ] \
|| fail "org.shift.icons must list preferences/scalable before actions/symbolic so System Settings category icons do not resolve to symbolic action aliases"
}
check_systemsettings_category_icons( ) {
[ [ -d /usr/share/systemsettings/categories ] ] || return 0
local missing_preferences
missing_preferences = " $( grep -Rh '^Icon=' /usr/share/systemsettings/categories 2>/dev/null \
| cut -d= -f2- \
| sort -u \
| while IFS = read -r icon_name; do
case " $icon_name " in
preferences-*)
[ [ -e " $theme_dir /preferences/scalable/ $icon_name .svg " ] ] || printf '%s\n' " $icon_name "
; ;
esac
done ) "
[ [ -z " $missing_preferences " ] ] || {
printf 'System Settings category icons must exist in preferences/scalable so they are not shadowed by symbolic action aliases:\n' >& 2
printf '%s\n' " $missing_preferences " | sed 's/^/ - /' >& 2
exit 1
}
}
check_systemsettings_module_icons( ) {
compgen -G '/usr/share/applications/kcm_*.desktop' >/dev/null || return 0
local missing_preferences
missing_preferences = " $( { grep -Rh '^Icon=' /usr/share/applications/kcm_*.desktop 2>/dev/null || true; } \
| cut -d= -f2- \
| sort -u \
| while IFS = read -r icon_name; do
[ [ -e " $theme_dir /preferences/scalable/ $icon_name .svg " ] ] || printf '%s\n' " $icon_name "
done ) "
[ [ -z " $missing_preferences " ] ] || {
printf 'System Settings module icons must exist in preferences/scalable so KCM list entries do not resolve to dark symbolic glyphs:\n' >& 2
printf '%s\n' " $missing_preferences " | sed 's/^/ - /' >& 2
exit 1
}
}
hicolor_fallback_exists( ) {
local icon_name = " $1 "
case " $icon_name " in
start-here-shift)
[ [ -e icons/sc-places-start-here-shift.svg ] ]
; ;
*)
return 1
; ;
esac
}
filter_icon_names( ) {
awk 'length > 0 && $0 !~ /[[:space:]]/ && $0 !~ /\// && $0 !~ /[][{}();:%>]/ && $0 !~ /^#/ && $0 !~ /\.(qml|png|jpg|jpeg|svg|desktop|service|json|xml|wav|ogg)$/ && $0 !~ /-$/ { print }' \
| grep -E " $icon_name_prefix_re "
}
collect_installed_plasma_icon_names( ) {
command -v rpm >/dev/null || return 0
local package
for package in \
plasma6-desktop \
plasma6-workspace \
plasma6-mobile \
plasma6-nm \
plasma6-pa \
powerdevil6 \
kscreen6 \
bluedevil6 \
systemsettings6 \
kde-cli-tools6 \
kwin6 \
kactivitymanagerd6; do
rpm -ql " $package " 2>/dev/null || true
done \
| awk '/\.(qml|js|json|desktop|actions)$/ || /metadata\.json$/ { print }' \
| { grep -v '^/usr/share/icons/' || true; } \
| sort -u > " $plasma_files "
[ [ -s " $plasma_files " ] ] || return 0
xargs -r perl -ne 'if (/^(?:Icon|IconName|X-KDE-PluginInfo-Icon)=(.+)$/) { print "$1\n"; } while (/"Icon"\s*:\s*"([^"]+)"/g) { print "$1\n"; } while (/(?:\bicon\b|icon\.name|iconName|iconSource|fallbackIconName|compactRepresentationIcon|categoryIcon|source)\s*:\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; } while (/(?:setIconName|iconName)\s*\(\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; }' < " $plasma_files " \
| filter_icon_names || true
}
collect_repo_icon_names( ) {
find \
components/mobileshell/qml \
containments/homescreens/folio/qml \
containments/homescreens/halcyon/qml \
containments/taskpanel/qml \
initialstart \
kcms \
kwin \
lookandfeel/contents/logout \
quicksettings \
-type f \( -name '*.qml' -o -name '*.js' -o -name '*.json' -o -name '*.desktop' -o -name '*.actions' -o -name '*.notifyrc' \) -print0 \
| xargs -0 perl -ne 'if (/^(?:Icon|IconName|X-KDE-PluginInfo-Icon)=(.+)$/) { print "$1\n"; } while (/"Icon"\s*:\s*"([^"]+)"/g) { print "$1\n"; } while (/(?:\bicon\b|icon\.name|iconName|iconSource|fallbackIconName|compactRepresentationIcon|categoryIcon|source)\s*:\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; } while (/(?:setIconName|iconName)\s*\(\s*["\x27]([^"\x27]+)["\x27]/g) { print "$1\n"; }' \
| filter_icon_names || true
}
[ [ -d " $theme_dir " ] ] || fail " Missing Shift icon theme directory: $theme_dir "
2026-05-17 16:31:58 +00:00
while IFS = read -r -d '' icon_path; do
[ [ -e " $icon_path " ] ] || printf '%s\n' " $icon_path "
done < <( find " $theme_dir " -type l -print0) > " $broken_icons "
2026-05-17 06:56:33 +00:00
if [ [ -s " $broken_icons " ] ] ; then
printf 'Broken Shift icon aliases:\n' >& 2
sed 's/^/ - /' " $broken_icons " >& 2
exit 1
fi
require_line lookandfeel/contents/defaults '^Theme=org\.shift\.icons$' \
"look-and-feel defaults must select org.shift.icons"
require_line " $theme_dir /index.theme " '^Inherits=hicolor$' \
"org.shift.icons must inherit only hicolor"
require_line " $theme_dir /index.theme " '^Directories=.*actions/symbolic.*places/symbolic.*apps/scalable' \
"org.shift.icons index.theme must list actions, places, and apps directories"
require_directory_order
require_line " $theme_dir /index.theme " '^\[actions/symbolic\]$' \
"org.shift.icons index.theme is missing [actions/symbolic]"
require_line " $theme_dir /index.theme " '^\[places/symbolic\]$' \
"org.shift.icons index.theme is missing [places/symbolic]"
require_line " $theme_dir /index.theme " '^\[apps/scalable\]$' \
"org.shift.icons index.theme is missing [apps/scalable]"
require_line " $theme_dir /index.theme " '^\[preferences/scalable\]$' \
"org.shift.icons index.theme is missing [preferences/scalable]"
check_systemsettings_category_icons
check_systemsettings_module_icons
{
collect_repo_icon_names
collect_installed_plasma_icon_names
if [ [ -d /usr/share/icons ] ] ; then
find /usr/share/icons -name 'network-*.svg' -o -name 'network-*.png' 2>/dev/null \
| sed 's#.*/##; s/\.[^.]*$//' \
| awk 'length > 0 { print }'
fi
cat <<'ICONS'
applications-all
applications-development
applications-education
applications-games
applications-graphics
applications-internet
applications-multimedia
applications-office
applications-other
applications-science
applications-system
applications-utilities
battery-000
battery-000-charging
battery-010
battery-010-charging
battery-020
battery-020-charging
battery-030
battery-030-charging
battery-040
battery-040-charging
battery-050
battery-050-charging
battery-060
battery-060-charging
battery-070
battery-070-charging
battery-080
battery-080-charging
battery-090
battery-090-charging
battery-100
battery-100-charging
battery-missing
network-bluetooth
network-bluetooth-activated
network-mobile-0
network-mobile-20
network-mobile-40
network-mobile-60
network-mobile-80
network-mobile-100
preferences-system
systemsettings
ICONS
} | sort -u > " $required_icons "
while IFS = read -r icon_name; do
[ [ -n " $icon_name " ] ] || continue
if icon_exists " $icon_name " || hicolor_fallback_exists " $icon_name " ; then
continue
fi
printf '%s\n' " $icon_name " >> " $missing_icons "
done < " $required_icons "
if [ [ -s " $missing_icons " ] ] ; then
printf 'Missing required Shift icons:\n' >& 2
sed 's/^/ - /' " $missing_icons " >& 2
exit 1
fi