mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-28 16:43:58 +00:00
3b52cd4c1b
Changes the script to produce rgb+mask images for 16x and 32x icons instead of png. Using icns files containing 16x and 32x png images for app bundles results in Finder misrendering icons for these sizes (even though the icns files are rendered correctly when viewed by themselves)
264 lines
9.6 KiB
Bash
264 lines
9.6 KiB
Bash
# shellcheck shell=bash
|
|
fixupOutputHooks+=('convertDesktopFiles $prefix')
|
|
|
|
# Get a param out of a desktop file. First parameter is the file and the second
|
|
# is the key who's value we should fetch.
|
|
getDesktopParam() {
|
|
local file="$1"
|
|
local key="$2"
|
|
local line k v
|
|
|
|
while read -r line; do
|
|
if [[ "$line" = *=* ]]; then
|
|
k="${line%%=*}"
|
|
v="${line#*=}"
|
|
|
|
if [[ "$k" = "$key" ]]; then
|
|
echo "$v"
|
|
return
|
|
fi
|
|
fi
|
|
done < "$file"
|
|
|
|
return 1
|
|
}
|
|
|
|
# Convert a freedesktop.org icon theme for a given app to a .icns file. When possible, missing
|
|
# icons are synthesized from SVG or rescaled from existing ones (when within the size threshold).
|
|
convertIconTheme() {
|
|
local -r out=$1
|
|
local -r sharePath=$2
|
|
local -r iconName=$3
|
|
local -r theme=${4:-hicolor}
|
|
|
|
# Sizes based on archived Apple documentation:
|
|
# https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes
|
|
local -ra iconSizes=(16 32 128 256 512)
|
|
local -ra scales=([1]="" [2]="@2")
|
|
|
|
# Based loosely on the algorithm at:
|
|
# https://specifications.freedesktop.org/icon-theme-spec/latest/#icon_lookup
|
|
# Assumes threshold = 2 for ease of implementation.
|
|
function findIcon() {
|
|
local -r iconSize=$1
|
|
local -r scale=$2
|
|
|
|
local scaleSuffix=${scales[$scale]}
|
|
local exactSize=${iconSize}x${iconSize}${scaleSuffix}
|
|
|
|
local -a validSizes=(
|
|
${exactSize}
|
|
$((iconSize + 1))x$((iconSize + 1))${scaleSuffix}
|
|
$((iconSize + 2))x$((iconSize + 2))${scaleSuffix}
|
|
$((iconSize - 1))x$((iconSize - 1))${scaleSuffix}
|
|
$((iconSize - 2))x$((iconSize - 2))${scaleSuffix}
|
|
)
|
|
|
|
local fallbackIcon=
|
|
|
|
for iconIndex in "${!candidateIcons[@]}"; do
|
|
for maybeSize in "${validSizes[@]}"; do
|
|
icon=${candidateIcons[$iconIndex]}
|
|
if [[ $icon = */$maybeSize/* ]]; then
|
|
if [[ $maybeSize = $exactSize ]]; then
|
|
echo "fixed $icon"
|
|
return 0
|
|
else
|
|
echo "threshold $icon"
|
|
return 0
|
|
fi
|
|
elif [[ -a $icon && -z "$fallbackIcon" ]]; then
|
|
fallbackIcon="$icon"
|
|
fi
|
|
done
|
|
done
|
|
|
|
if [[ -n "$fallbackIcon" ]]; then
|
|
echo "fallback $fallbackIcon"
|
|
return 0
|
|
fi
|
|
|
|
echo "scalable"
|
|
}
|
|
|
|
function resizeIcon() {
|
|
local -r in=$1
|
|
local -r out=$2
|
|
local -r iconSize=$3
|
|
local -r scale=$4
|
|
|
|
local density=$((72 * scale))x$((72 * scale))
|
|
local dim=$((iconSize * scale))
|
|
|
|
echo "desktopToDarwinBundle: resizing icon $in to $out, size $dim" >&2
|
|
magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
|
|
convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
|
|
}
|
|
|
|
function synthesizeIcon() {
|
|
local -r in=$1
|
|
local -r out=$2
|
|
local -r iconSize=$3
|
|
local -r scale=$4
|
|
|
|
if [[ $in != '-' ]]; then
|
|
local density=$((72 * scale))x$((72 * scale))
|
|
local dim=$((iconSize * scale))
|
|
|
|
echo "desktopToDarwinBundle: rasterizing svg $in to $out, size $dim" >&2
|
|
rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
|
|
magick convert -density "$density" -units PixelsPerInch "$out" "$out"
|
|
convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# macOS does not correctly display 16x and 32x png icons on app bundles
|
|
# they need to be converted to rgb+mask (argb is supported only from macOS 11)
|
|
function convertIfUnsupportedIcon() {
|
|
local -r in=$1
|
|
local -r iconSize=$2
|
|
local -r scale=$3
|
|
local -r out=${in%.png}.rgb
|
|
|
|
if [[ ($scale -eq 1) && ($iconSize -eq 32 || $iconSize -eq 16) ]]; then
|
|
echo "desktopToDarwinBundle: converting ${iconSize}x icon to rgb" >&2
|
|
icnsutil convert "$out" "$in"
|
|
rm "$in"
|
|
fi
|
|
}
|
|
|
|
function getIcons() {
|
|
local -r sharePath=$1
|
|
local -r iconname=$2
|
|
local -r theme=$3
|
|
local -r resultdir=$(mktemp -d)
|
|
|
|
local -ar candidateIcons=(
|
|
"${sharePath}/icons/${theme}/"*"/${iconname}.png"
|
|
"${sharePath}/icons/${theme}/"*"/${iconname}.xpm"
|
|
)
|
|
|
|
local -a scalableIcon=("${sharePath}/icons/${theme}/scalable/${iconname}.svg"*)
|
|
if [[ ${#scalableIcon[@]} = 0 ]]; then
|
|
scalableIcon=('-')
|
|
fi
|
|
|
|
# Tri-state variable, NONE means no icons have been found, an empty
|
|
# icns file will be generated, not sure that's necessary because macOS
|
|
# will default to a generic icon if no icon can be found.
|
|
#
|
|
# OTHER means an appropriate icon was found.
|
|
#
|
|
# Any other value is a path to an icon file that isn't scalable or
|
|
# within the threshold. This is used as a fallback in case no better
|
|
# icon can be found and will be scaled as much as
|
|
# necessary to result in appropriate icon sizes.
|
|
local foundIcon=NONE
|
|
for iconSize in "${iconSizes[@]}"; do
|
|
for scale in "${!scales[@]}"; do
|
|
local iconResult=$(findIcon $iconSize $scale)
|
|
local type=${iconResult%% *}
|
|
local icon=${iconResult#* }
|
|
local scaleSuffix=${scales[$scale]}
|
|
local result=${resultdir}/${iconSize}x${iconSize}${scales[$scale]}${scaleSuffix:+x}.png
|
|
echo "desktopToDarwinBundle: using $type icon $icon for size $iconSize$scaleSuffix" >&2
|
|
case $type in
|
|
fixed)
|
|
local density=$((72 * scale))x$((72 * scale))
|
|
magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
|
|
convertIfUnsupportedIcon "$result" "$iconSize" "$scale"
|
|
foundIcon=OTHER
|
|
;;
|
|
threshold)
|
|
# Synthesize an icon of the exact size if a scalable icon is available
|
|
# instead of scaling one and ending up with a fuzzy icon.
|
|
if ! synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale"; then
|
|
resizeIcon "$icon" "$result" "$iconSize" "$scale"
|
|
fi
|
|
foundIcon=OTHER
|
|
;;
|
|
scalable)
|
|
synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
|
|
foundIcon=OTHER
|
|
;;
|
|
fallback)
|
|
# Use the largest size available to scale to
|
|
# appropriate sizes.
|
|
if [[ $foundIcon != OTHER ]]; then
|
|
foundIcon=$icon
|
|
fi
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
if [[ $foundIcon != NONE && $foundIcon != OTHER ]]; then
|
|
# Ideally we'd only resize to whatever the closest sizes are,
|
|
# starting from whatever icon sizes are available.
|
|
for iconSize in 16 32 128 256 512; do
|
|
local result=${resultdir}/${iconSize}x${iconSize}.png
|
|
resizeIcon "$foundIcon" "$result" "$iconSize" 1
|
|
done
|
|
fi
|
|
echo "$resultdir"
|
|
}
|
|
|
|
iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
|
|
if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
|
|
icnsutil compose --toc "$out/${iconName}.icns" "$iconsdir/"*
|
|
else
|
|
echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
|
|
touch "$out/${iconName}.icns"
|
|
fi
|
|
}
|
|
|
|
processExecFieldCodes() {
|
|
local -r file=$1
|
|
local -r execRaw=$(getDesktopParam "${file}" "Exec")
|
|
local -r execNoK="${execRaw/\%k/${file}}"
|
|
local -r execNoKC="${execNoK/\%c/$(getDesktopParam "${file}" "Name")}"
|
|
local -r icon=$(getDesktopParam "${file}" "Icon")
|
|
local -r execNoKCI="${execNoKC/\%i/${icon:+--icon }${icon}}"
|
|
local -r execNoKCIfu="${execNoKCI/ \%[fu]/}"
|
|
local -r exec="${execNoKCIfu/ \%[FU]/}"
|
|
if [[ "$exec" != "$execRaw" ]]; then
|
|
echo 1>&2 "desktopToDarwinBundle: Application bundles do not understand desktop entry field codes. Changed '$execRaw' to '$exec'."
|
|
fi
|
|
echo "$exec"
|
|
}
|
|
|
|
# For a given .desktop file, generate a darwin '.app' bundle for it.
|
|
convertDesktopFile() {
|
|
local -r file=$1
|
|
local -r sharePath=$(dirname "$(dirname "$file")")
|
|
local -r name=$(getDesktopParam "${file}" "Name")
|
|
local -r macOSExec=$(getDesktopParam "${file}" "X-macOS-Exec")
|
|
if [[ "$macOSExec" ]]; then
|
|
local -r exec="$macOSExec"
|
|
else
|
|
local -r exec=$(processExecFieldCodes "${file}")
|
|
fi
|
|
local -r iconName=$(getDesktopParam "${file}" "Icon")
|
|
local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
|
|
|
|
mkdir -p "${!outputBin}/Applications/${name}.app/Contents/MacOS"
|
|
mkdir -p "${!outputBin}/Applications/${name}.app/Contents/Resources"
|
|
|
|
convertIconTheme "${!outputBin}/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
|
|
|
|
write-darwin-bundle "${!outputBin}" "$name" "$exec" "$iconName" "$squircle"
|
|
}
|
|
|
|
convertDesktopFiles() {
|
|
local dir="$1/share/applications/"
|
|
|
|
if [ -d "${dir}" ]; then
|
|
for desktopFile in $(find "$dir" -iname "*.desktop"); do
|
|
convertDesktopFile "$desktopFile";
|
|
done
|
|
fi
|
|
}
|