mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 00:47:22 +00:00
Kirigami.Icon { isMask: true } flattens an SVG to its alpha, so when a quick-setting tile resolves to a colored preferences/scalable glyph the whole rounded background collapses into a solid square. Append -symbolic to the five icon names that collided with colored tiles and add symlink aliases where no real symbolic variant exists yet. Guard the regression with a check in tests/check-shift-icon-theme.sh.
283 lines
No EOL
10 KiB
Bash
283 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" '^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 |