nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1# shellcheck shell=bash
2fixupOutputHooks+=('convertDesktopFiles $prefix')
3
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.
6getDesktopParam() {
7 local file="$1"
8 local key="$2"
9 local line k v
10
11 while read -r line; do
12 if [[ "$line" = *=* ]]; then
13 k="${line%%=*}"
14 v="${line#*=}"
15
16 if [[ "$k" = "$key" ]]; then
17 echo "$v"
18 return
19 fi
20 fi
21 done < "$file"
22
23 return 1
24}
25
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).
28convertIconTheme() {
29 local -r out=$1
30 local -r sharePath=$2
31 local -r iconName=$3
32 local -r theme=${4:-hicolor}
33
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")
38
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
45
46 local scaleSuffix=${scales[$scale]}
47 local exactSize=${iconSize}x${iconSize}${scaleSuffix}
48
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}
55 )
56
57 local fallbackIcon=
58
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
69 fi
70 elif [[ -a $icon && -z "$fallbackIcon" ]]; then
71 fallbackIcon="$icon"
72 fi
73 done
74 done
75
76 if [[ -n "$fallbackIcon" ]]; then
77 echo "fallback $fallbackIcon"
78 return 0
79 fi
80
81 echo "scalable"
82 }
83
84 function resizeIcon() {
85 local -r in=$1
86 local -r out=$2
87 local -r iconSize=$3
88 local -r scale=$4
89
90 local density=$((72 * scale))x$((72 * scale))
91 local dim=$((iconSize * scale))
92
93 echo "desktopToDarwinBundle: resizing icon $in to $out, size $dim" >&2
94 magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
95 convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
96 }
97
98 function synthesizeIcon() {
99 local -r in=$1
100 local -r out=$2
101 local -r iconSize=$3
102 local -r scale=$4
103
104 if [[ $in != '-' ]]; then
105 local density=$((72 * scale))x$((72 * scale))
106 local dim=$((iconSize * scale))
107
108 echo "desktopToDarwinBundle: rasterizing svg $in to $out, size $dim" >&2
109 rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
110 magick convert -density "$density" -units PixelsPerInch "$out" "$out"
111 convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
112 else
113 return 1
114 fi
115 }
116
117 # macOS does not correctly display 16x and 32x png icons on app bundles
118 # they need to be converted to argb
119 function convertIfUnsupportedIcon() {
120 local -r in=$1
121 local -r iconSize=$2
122 local -r scale=$3
123 local -r out=${in%.png}.argb
124
125 if [[ ($scale -eq 1) && ($iconSize -eq 32 || $iconSize -eq 16) ]]; then
126 echo "desktopToDarwinBundle: converting ${iconSize}x icon to argb" >&2
127 icnsutil convert "$out" "$in"
128 rm "$in"
129 fi
130 }
131
132 function getIcons() {
133 local -r sharePath=$1
134 local -r iconname=$2
135 local -r theme=$3
136 local -r resultdir=$(mktemp -d)
137
138 local -ar candidateIcons=(
139 "${sharePath}/icons/${theme}/"*"/${iconname}.png"
140 "${sharePath}/icons/${theme}/"*"/${iconname}.xpm"
141 )
142
143 local -a scalableIcon=("${sharePath}/icons/${theme}/scalable/${iconname}.svg"*)
144 if [[ ${#scalableIcon[@]} = 0 ]]; then
145 scalableIcon=('-')
146 fi
147
148 # Tri-state variable, NONE means no icons have been found, an empty
149 # icns file will be generated, not sure that's necessary because macOS
150 # will default to a generic icon if no icon can be found.
151 #
152 # OTHER means an appropriate icon was found.
153 #
154 # Any other value is a path to an icon file that isn't scalable or
155 # within the threshold. This is used as a fallback in case no better
156 # icon can be found and will be scaled as much as
157 # necessary to result in appropriate icon sizes.
158 local foundIcon=NONE
159 for iconSize in "${iconSizes[@]}"; do
160 for scale in "${!scales[@]}"; do
161 local iconResult=$(findIcon $iconSize $scale)
162 local type=${iconResult%% *}
163 local icon=${iconResult#* }
164 local scaleSuffix=${scales[$scale]}
165 local result=${resultdir}/${iconSize}x${iconSize}${scales[$scale]}${scaleSuffix:+x}.png
166 echo "desktopToDarwinBundle: using $type icon $icon for size $iconSize$scaleSuffix" >&2
167 case $type in
168 fixed)
169 local density=$((72 * scale))x$((72 * scale))
170 magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
171 convertIfUnsupportedIcon "$result" "$iconSize" "$scale"
172 foundIcon=OTHER
173 ;;
174 threshold)
175 # Synthesize an icon of the exact size if a scalable icon is available
176 # instead of scaling one and ending up with a fuzzy icon.
177 if ! synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale"; then
178 resizeIcon "$icon" "$result" "$iconSize" "$scale"
179 fi
180 foundIcon=OTHER
181 ;;
182 scalable)
183 synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
184 foundIcon=OTHER
185 ;;
186 fallback)
187 # Use the largest size available to scale to
188 # appropriate sizes.
189 if [[ $foundIcon != OTHER ]]; then
190 foundIcon=$icon
191 fi
192 ;;
193 *)
194 ;;
195 esac
196 done
197 done
198 if [[ $foundIcon != NONE && $foundIcon != OTHER ]]; then
199 # Ideally we'd only resize to whatever the closest sizes are,
200 # starting from whatever icon sizes are available.
201 for iconSize in 16 32 128 256 512; do
202 local result=${resultdir}/${iconSize}x${iconSize}.png
203 resizeIcon "$foundIcon" "$result" "$iconSize" 1
204 done
205 fi
206 echo "$resultdir"
207 }
208
209 iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
210 if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
211 icnsutil compose --toc "$out/${iconName}.icns" "$iconsdir/"*
212 else
213 echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
214 touch "$out/${iconName}.icns"
215 fi
216}
217
218processExecFieldCodes() {
219 local -r file=$1
220 local -r execRaw=$(getDesktopParam "${file}" "Exec")
221 local -r execNoK="${execRaw/\%k/${file}}"
222 local -r execNoKC="${execNoK/\%c/$(getDesktopParam "${file}" "Name")}"
223 local -r icon=$(getDesktopParam "${file}" "Icon")
224 local -r execNoKCI="${execNoKC/\%i/${icon:+--icon }${icon}}"
225 local -r execNoKCIfu="${execNoKCI/ \%[fu]/}"
226 local -r exec="${execNoKCIfu/ \%[FU]/}"
227 if [[ "$exec" != "$execRaw" ]]; then
228 echo 1>&2 "desktopToDarwinBundle: Application bundles do not understand desktop entry field codes. Changed '$execRaw' to '$exec'."
229 fi
230 echo "$exec"
231}
232
233# For a given .desktop file, generate a darwin '.app' bundle for it.
234convertDesktopFile() {
235 local -r file=$1
236 local -r sharePath=$(dirname "$(dirname "$file")")
237 local -r name=$(getDesktopParam "${file}" "Name")
238 local -r macOSExec=$(getDesktopParam "${file}" "X-macOS-Exec")
239 if [[ "$macOSExec" ]]; then
240 local -r exec="$macOSExec"
241 else
242 local -r exec=$(processExecFieldCodes "${file}")
243 fi
244 local -r iconName=$(getDesktopParam "${file}" "Icon")
245 local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
246
247 mkdir -p "${!outputBin}/Applications/${name}.app/Contents/MacOS"
248 mkdir -p "${!outputBin}/Applications/${name}.app/Contents/Resources"
249
250 convertIconTheme "${!outputBin}/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
251
252 write-darwin-bundle "${!outputBin}" "$name" "$exec" "$iconName" "$squircle"
253}
254
255convertDesktopFiles() {
256 local dir="$1/share/applications/"
257
258 if [ -d "${dir}" ]; then
259 for desktopFile in $(find "$dir" -iname "*.desktop"); do
260 convertDesktopFile "$desktopFile";
261 done
262 fi
263}