mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 08:57:21 +00:00
Declare FollowsColorScheme=true for the Shift icon theme so KDE apps recolor symbolic Places glyphs against dark and light surfaces. Document the requirement and guard it in the icon theme coverage test.
285 lines
No EOL
10 KiB
Bash
285 lines
No EOL
10 KiB
Bash
#!/usr/bin/env bash
|
|
# SPDX-FileCopyrightText: 2026 Marco Allegretti <mightymarco4@gmail.com>
|
|
# SPDX-License-Identifier: EUPL-1.2
|
|
|
|
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"
|
|
|
|
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"
|
|
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" '^FollowsColorScheme=true$' \
|
|
"org.shift.icons must follow the active color scheme so symbolic Places icons stay legible in dark and light themes"
|
|
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
|
|
|
|
# Quick-setting tiles render with Kirigami.Icon { isMask: true }; if their icon
|
|
# name resolves to a colored preferences/scalable tile it collapses to a solid
|
|
# rounded square. Require an explicit -symbolic suffix for any quick-setting
|
|
# icon name that also exists as a colored tile.
|
|
masked_collisions="$(mktemp)"
|
|
trap 'rm -f "$required_icons" "$missing_icons" "$broken_icons" "$plasma_files" "$masked_collisions"' EXIT
|
|
|
|
while IFS= read -r -d '' qml; do
|
|
perl -ne 'if (/^\s*icon:\s*"([^"]+)"\s*$/) { print "$1\n"; }' "$qml"
|
|
done < <(find quicksettings -name 'main.qml' -print0) \
|
|
| sort -u \
|
|
| while IFS= read -r icon_name; do
|
|
case "$icon_name" in
|
|
*-symbolic) continue ;;
|
|
esac
|
|
[[ -e "$theme_dir/preferences/scalable/$icon_name.svg" ]] || continue
|
|
printf '%s\n' "$icon_name"
|
|
done > "$masked_collisions"
|
|
|
|
if [[ -s "$masked_collisions" ]]; then
|
|
printf 'Quick-setting icon names must use a -symbolic suffix when a colored preferences/scalable tile exists, otherwise the masked tile renders as a solid square:\n' >&2
|
|
sed 's/^/ - /' "$masked_collisions" >&2
|
|
exit 1
|
|
fi |