summary refs log tree commit diff
path: root/pkgs/build-support/setup-hooks
diff options
context:
space:
mode:
authorRandy Eckenrode <randy@largeandhighquality.com>2022-02-19 23:30:16 -0500
committerRandy Eckenrode <randy@largeandhighquality.com>2022-02-23 20:29:52 -0500
commit30a09ae9ac11c659e71c34ec2728608a5a6dee3f (patch)
tree396fa3e5bd171ca88aa4c1c9573bdba112cb646c /pkgs/build-support/setup-hooks
parent88ac8585ba2e443c1075d28f3d7761f18193eb1b (diff)
downloadnixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar.gz
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar.bz2
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar.lz
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar.xz
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.tar.zst
nixpkgs-30a09ae9ac11c659e71c34ec2728608a5a6dee3f.zip
desktopToDarwinBundle: fix squircle icons
- Convert icons to a single .icns file; and
- Provide an opt-out via X-macOS-Squircle in the desktop item to
  override the squircle behavior when the source icons look bad when
  converted automatically.
Diffstat (limited to 'pkgs/build-support/setup-hooks')
-rw-r--r--pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh148
1 files changed, 132 insertions, 16 deletions
diff --git a/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh b/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh
index d1175d3a5f9..480bcde6c1c 100644
--- a/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh
+++ b/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh
@@ -10,31 +10,147 @@ getDesktopParam() {
     awk -F "=" "/${pattern}/ {print \$2}" "${file}"
 }
 
+# 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}
+
+    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/icon-theme-spec-latest.html#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}
+            $(expr $iconSize + 1)x$(expr $iconSize + 1)${scaleSuffix}
+            $(expr $iconSize + 2)x$(expr $iconSize + 2)${scaleSuffix}
+            $(expr $iconSize - 1)x$(expr $iconSize - 1)${scaleSuffix}
+            $(expr $iconSize - 2)x$(expr $iconSize - 2)${scaleSuffix}
+        )
+
+        for iconIndex in "${!candidateIcons[@]}"; do
+            for maybeSize in "${validSizes[@]}"; do
+                icon=${candidateIcons[$iconIndex]}
+                if [[ $icon = */$maybeSize/* ]]; then
+                    if [[ $maybeSize = $exactSize ]]; then
+                        echo "fixed $icon"
+                    else
+                        echo "threshold $icon"
+                    fi
+                    return 0
+                fi
+            done
+        done
+        echo "scalable"
+    }
+
+    function resizeIcon() {
+        local -r in=$1
+        local -r out=$2
+        local -r iconSize=$3
+        local -r scale=$4
+
+        local density=$(expr 72 \* $scale)x$(expr 72 \* $scale)
+        local dim=$(expr $iconSize \* $scale)
+
+        magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
+    }
+
+    function synthesizeIcon() {
+        local -r in=$1
+        local -r out=$2
+        local -r iconSize=$3
+        local -r scale=$4
+
+        if [[ $in != '-' ]]; then
+            local density=$(expr 72 \* $scale)x$(expr 72 \* $scale)
+            local dim=$(expr $iconSize \* $scale)
+            rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
+            magick convert -density "$density" -units PixelsPerInch "$out" "$out"
+        else
+            return 1
+        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
+
+        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
+                case $type in
+                    fixed)
+                        local density=$(expr 72 \* $scale)x$(expr 72 \* $scale)
+                        magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
+                        ;;
+                    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
+                        ;;
+                    scalable)
+                        synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
+                        ;;
+                esac
+            done
+        done
+        echo "$resultdir"
+    }
+
+    iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
+    if [[ ! -z "$(ls -1 "$iconsdir/"*)" ]]; then
+        icnsutil compose "$out/${iconName}.icns" "$iconsdir/"*
+    else
+        echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
+        touch "$out/${iconName}.icns"
+    fi
+}
+
 # For a given .desktop file, generate a darwin '.app' bundle for it.
 convertDesktopFile() {
-    local -r file="$1"
+    local -r file=$1
+    local -r sharePath=$(dirname "$(dirname "$file")")
     local -r name=$(getDesktopParam "${file}" "^Name")
     local -r exec=$(getDesktopParam "${file}" "Exec")
-    local -r iconName=$(getDesktopParam "${file}" "Icon")
-    local -r iconFiles=$(find "$out/share/icons/" -name "${iconName}.*" 2>/dev/null);
-    local -r pixMaps=$(find "$out/share/pixmaps/" -name "${iconName}.xpm" 2>/dev/null);
+    local -r iconName=$(getDesktopParam "${file}" "^Icon")
+    local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
 
     mkdir -p "$out/Applications/${name}.app/Contents/MacOS"
     mkdir -p "$out/Applications/${name}.app/Contents/Resources"
 
-    local i=0;
-    for icon in $iconFiles; do
-      ln -s "$icon" "$out/Applications/${name}.app/Contents/Resources/$i-$(basename "$icon")"
-      (( i +=1 ));
-    done
-
-    for pixmap in $pixMaps; do
-      local newIconName="$i-$(basename "$pixmap")";
-      convert "$pixmap" "$out/Applications/${name}.app/Contents/Resources/${newIconName%.xpm}.png"
-      (( i +=1 ));
-    done
+    convertIconTheme "$out/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
 
-    write-darwin-bundle "$out" "$name" "$exec"
+    write-darwin-bundle "$out" "$name" "$exec" "$iconName" "$squircle"
 }
 
 convertDesktopFiles() {