pytrainer: unpin python 3.10
[NixPkgs.git] / pkgs / build-support / setup-hooks / desktop-to-darwin-bundle.sh
blobd7cab0e81a6095f3f9d1dac9eca46a69a1adfc70
1 # shellcheck shell=bash
2 fixupOutputHooks+=('convertDesktopFiles $prefix')
4 # Get a param out of a desktop file. First parameter is the file and the second
5 # is the key who's value we should fetch.
6 getDesktopParam() {
7 local file="$1"
8 local key="$2"
9 local line k v
11 while read -r line; do
12 if [[ "$line" = *=* ]]; then
13 k="${line%%=*}"
14 v="${line#*=}"
16 if [[ "$k" = "$key" ]]; then
17 echo "$v"
18 return
21 done < "$file"
23 return 1
26 # Convert a freedesktop.org icon theme for a given app to a .icns file. When possible, missing
27 # icons are synthesized from SVG or rescaled from existing ones (when within the size threshold).
28 convertIconTheme() {
29 local -r out=$1
30 local -r sharePath=$2
31 local -r iconName=$3
32 local -r theme=${4:-hicolor}
34 # Sizes based on archived Apple documentation:
35 # https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes
36 local -ra iconSizes=(16 32 128 256 512)
37 local -ra scales=([1]="" [2]="@2")
39 # Based loosely on the algorithm at:
40 # https://specifications.freedesktop.org/icon-theme-spec/latest/#icon_lookup
41 # Assumes threshold = 2 for ease of implementation.
42 function findIcon() {
43 local -r iconSize=$1
44 local -r scale=$2
46 local scaleSuffix=${scales[$scale]}
47 local exactSize=${iconSize}x${iconSize}${scaleSuffix}
49 local -a validSizes=(
50 ${exactSize}
51 $((iconSize + 1))x$((iconSize + 1))${scaleSuffix}
52 $((iconSize + 2))x$((iconSize + 2))${scaleSuffix}
53 $((iconSize - 1))x$((iconSize - 1))${scaleSuffix}
54 $((iconSize - 2))x$((iconSize - 2))${scaleSuffix}
57 local fallbackIcon=
59 for iconIndex in "${!candidateIcons[@]}"; do
60 for maybeSize in "${validSizes[@]}"; do
61 icon=${candidateIcons[$iconIndex]}
62 if [[ $icon = */$maybeSize/* ]]; then
63 if [[ $maybeSize = $exactSize ]]; then
64 echo "fixed $icon"
65 return 0
66 else
67 echo "threshold $icon"
68 return 0
70 elif [[ -a $icon && -z "$fallbackIcon" ]]; then
71 fallbackIcon="$icon"
73 done
74 done
76 if [[ -n "$fallbackIcon" ]]; then
77 echo "fallback $fallbackIcon"
78 return 0
81 echo "scalable"
84 function resizeIcon() {
85 local -r in=$1
86 local -r out=$2
87 local -r iconSize=$3
88 local -r scale=$4
90 local density=$((72 * scale))x$((72 * scale))
91 local dim=$((iconSize * scale))
93 echo "desktopToDarwinBundle: resizing icon $in to $out, size $dim" >&2
94 magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
97 function synthesizeIcon() {
98 local -r in=$1
99 local -r out=$2
100 local -r iconSize=$3
101 local -r scale=$4
103 if [[ $in != '-' ]]; then
104 local density=$((72 * scale))x$((72 * scale))
105 local dim=$((iconSize * scale))
107 echo "desktopToDarwinBundle: rasterizing svg $in to $out, size $dim" >&2
108 rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
109 magick convert -density "$density" -units PixelsPerInch "$out" "$out"
110 else
111 return 1
115 function getIcons() {
116 local -r sharePath=$1
117 local -r iconname=$2
118 local -r theme=$3
119 local -r resultdir=$(mktemp -d)
121 local -ar candidateIcons=(
122 "${sharePath}/icons/${theme}/"*"/${iconname}.png"
123 "${sharePath}/icons/${theme}/"*"/${iconname}.xpm"
126 local -a scalableIcon=("${sharePath}/icons/${theme}/scalable/${iconname}.svg"*)
127 if [[ ${#scalableIcon[@]} = 0 ]]; then
128 scalableIcon=('-')
131 # Tri-state variable, NONE means no icons have been found, an empty
132 # icns file will be generated, not sure that's necessary because macOS
133 # will default to a generic icon if no icon can be found.
135 # OTHER means an appropriate icon was found.
137 # Any other value is a path to an icon file that isn't scalable or
138 # within the threshold. This is used as a fallback in case no better
139 # icon can be found and will be scaled as much as
140 # necessary to result in appropriate icon sizes.
141 local foundIcon=NONE
142 for iconSize in "${iconSizes[@]}"; do
143 for scale in "${!scales[@]}"; do
144 local iconResult=$(findIcon $iconSize $scale)
145 local type=${iconResult%% *}
146 local icon=${iconResult#* }
147 local scaleSuffix=${scales[$scale]}
148 local result=${resultdir}/${iconSize}x${iconSize}${scales[$scale]}${scaleSuffix:+x}.png
149 echo "desktopToDarwinBundle: using $type icon $icon for size $iconSize$scaleSuffix" >&2
150 case $type in
151 fixed)
152 local density=$((72 * scale))x$((72 * scale))
153 magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
154 foundIcon=OTHER
156 threshold)
157 # Synthesize an icon of the exact size if a scalable icon is available
158 # instead of scaling one and ending up with a fuzzy icon.
159 if ! synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale"; then
160 resizeIcon "$icon" "$result" "$iconSize" "$scale"
162 foundIcon=OTHER
164 scalable)
165 synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
166 foundIcon=OTHER
168 fallback)
169 # Use the largest size available to scale to
170 # appropriate sizes.
171 if [[ $foundIcon != OTHER ]]; then
172 foundIcon=$icon
177 esac
178 done
179 done
180 if [[ $foundIcon != NONE && $foundIcon != OTHER ]]; then
181 # Ideally we'd only resize to whatever the closest sizes are,
182 # starting from whatever icon sizes are available.
183 for iconSize in 16 32 128 256 512; do
184 local result=${resultdir}/${iconSize}x${iconSize}.png
185 resizeIcon "$foundIcon" "$result" "$iconSize" 1
186 done
188 echo "$resultdir"
191 iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
192 if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
193 icnsutil compose --toc "$out/${iconName}.icns" "$iconsdir/"*
194 else
195 echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
196 touch "$out/${iconName}.icns"
200 processExecFieldCodes() {
201 local -r file=$1
202 local -r execRaw=$(getDesktopParam "${file}" "Exec")
203 local -r execNoK="${execRaw/\%k/${file}}"
204 local -r execNoKC="${execNoK/\%c/$(getDesktopParam "${file}" "Name")}"
205 local -r icon=$(getDesktopParam "${file}" "Icon")
206 local -r execNoKCI="${execNoKC/\%i/${icon:+--icon }${icon}}"
207 local -r execNoKCIfu="${execNoKCI/ \%[fu]/}"
208 local -r exec="${execNoKCIfu/ \%[FU]/}"
209 if [[ "$exec" != "$execRaw" ]]; then
210 echo 1>&2 "desktopToDarwinBundle: Application bundles do not understand desktop entry field codes. Changed '$execRaw' to '$exec'."
212 echo "$exec"
215 # For a given .desktop file, generate a darwin '.app' bundle for it.
216 convertDesktopFile() {
217 local -r file=$1
218 local -r sharePath=$(dirname "$(dirname "$file")")
219 local -r name=$(getDesktopParam "${file}" "Name")
220 local -r macOSExec=$(getDesktopParam "${file}" "X-macOS-Exec")
221 if [[ "$macOSExec" ]]; then
222 local -r exec="$macOSExec"
223 else
224 local -r exec=$(processExecFieldCodes "${file}")
226 local -r iconName=$(getDesktopParam "${file}" "Icon")
227 local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
229 mkdir -p "${!outputBin}/Applications/${name}.app/Contents/MacOS"
230 mkdir -p "${!outputBin}/Applications/${name}.app/Contents/Resources"
232 convertIconTheme "${!outputBin}/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
234 write-darwin-bundle "${!outputBin}" "$name" "$exec" "$iconName" "$squircle"
237 convertDesktopFiles() {
238 local dir="$1/share/applications/"
240 if [ -d "${dir}" ]; then
241 for desktopFile in $(find "$dir" -iname "*.desktop"); do
242 convertDesktopFile "$desktopFile";
243 done