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.
11 while read -r line
; do
12 if [[ "$line" = *=* ]]; then
16 if [[ "$k" = "$key" ]]; then
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).
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.
46 local scaleSuffix
=${scales[$scale]}
47 local exactSize
=${iconSize}x${iconSize}${scaleSuffix}
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}
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
67 echo "threshold $icon"
70 elif [[ -a $icon && -z "$fallbackIcon" ]]; then
76 if [[ -n "$fallbackIcon" ]]; then
77 echo "fallback $fallbackIcon"
84 function resizeIcon
() {
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
() {
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"
115 function getIcons
() {
116 local -r sharePath
=$1
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
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.
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
152 local density
=$
((72 * scale
))x$
((72 * scale
))
153 magick convert
-density "$density" -units PixelsPerInch
"$icon" "$result"
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"
165 synthesizeIcon
"${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
169 # Use the largest size available to scale to
171 if [[ $foundIcon != OTHER
]]; then
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
191 iconsdir
=$
(getIcons
"$sharePath" "apps/${iconName}" "$theme")
192 if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
193 icnsutil compose
--toc "$out/${iconName}.icns" "$iconsdir/"*
195 echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
196 touch "$out/${iconName}.icns"
200 processExecFieldCodes
() {
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'."
215 # For a given .desktop file, generate a darwin '.app' bundle for it.
216 convertDesktopFile
() {
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"
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";