Merge pull request #161003 from reckenrode/squircle-fix
desktopToDarwinBundle: fix squircle icons
This commit is contained in:
commit
5c9e0c9459
|
@ -4,33 +4,34 @@ let
|
|||
pListText = lib.generators.toPlist { } {
|
||||
CFBundleDevelopmentRegion = "English";
|
||||
CFBundleExecutable = "$name";
|
||||
CFBundleIconFiles = [ "$iconPlistArray" ];
|
||||
CFBundleIconFile = "$icon";
|
||||
CFBundleIdentifier = "org.nixos.$name";
|
||||
CFBundleInfoDictionaryVersion = "6.0";
|
||||
CFBundleName = "$name";
|
||||
CFBundlePackageType = "APPL";
|
||||
CFBundleSignature = "???";
|
||||
};
|
||||
|
||||
# The generation of the CFBundleIconFiles array is a bit of a hack, since we
|
||||
# will always end up with an empty first element (<string></string>) but macOS
|
||||
# appears to ignore this which allows us to use the nix PList generator.
|
||||
in writeScriptBin "write-darwin-bundle" ''
|
||||
shopt -s nullglob
|
||||
|
||||
readonly prefix="$1"
|
||||
readonly name="$2"
|
||||
readonly exec="$3"
|
||||
iconPlistArray=""
|
||||
readonly prefix=$1
|
||||
readonly name=$2
|
||||
readonly exec=$3
|
||||
readonly icon=$4.icns
|
||||
readonly squircle=''${5:-1}
|
||||
readonly plist=$prefix/Applications/$name.app/Contents/Info.plist
|
||||
|
||||
for icon in "$prefix/Applications/$name.app/Contents/Resources"/*; do
|
||||
iconPlistArray="$iconPlistArray</string><string>"$(basename "$icon")""
|
||||
done
|
||||
|
||||
cat > "$prefix/Applications/$name.app/Contents/Info.plist" <<EOF
|
||||
cat > "$plist" <<EOF
|
||||
${pListText}
|
||||
EOF
|
||||
|
||||
if [[ $squircle != 0 && $squircle != "false" ]]; then
|
||||
sed "
|
||||
s|CFBundleIconFile|CFBundleIconFiles|;
|
||||
s|<string>$icon</string>|<array><string>$icon</string></array>|
|
||||
" -i "$plist"
|
||||
fi
|
||||
|
||||
cat > "$prefix/Applications/$name.app/Contents/MacOS/$name" <<EOF
|
||||
#!/bin/bash
|
||||
exec $prefix/bin/$exec
|
||||
|
|
|
@ -10,31 +10,156 @@ 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 48 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}
|
||||
|
||||
if [[ $exactSize = '48x48@2' ]]; then
|
||||
# macOS does not support a 2x scale variant of 48x48 icons
|
||||
# See: https://en.wikipedia.org/wiki/Apple_Icon_Image_format#Icon_types
|
||||
echo "unsupported"
|
||||
return 0
|
||||
fi
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
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=$((72 * scale))x$((72 * scale))
|
||||
local dim=$((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=$((72 * scale))x$((72 * scale))
|
||||
local dim=$((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=$((72 * scale))x$((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
|
||||
convertIconTheme "$out/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
|
||||
|
||||
for pixmap in $pixMaps; do
|
||||
local newIconName="$i-$(basename "$pixmap")";
|
||||
convert "$pixmap" "$out/Applications/${name}.app/Contents/Resources/${newIconName%.xpm}.png"
|
||||
(( i +=1 ));
|
||||
done
|
||||
|
||||
write-darwin-bundle "$out" "$name" "$exec"
|
||||
write-darwin-bundle "$out" "$name" "$exec" "$iconName" "$squircle"
|
||||
}
|
||||
|
||||
convertDesktopFiles() {
|
||||
|
|
31
pkgs/development/python-modules/icnsutil/default.nix
Normal file
31
pkgs/development/python-modules/icnsutil/default.nix
Normal file
|
@ -0,0 +1,31 @@
|
|||
{ lib
|
||||
, python
|
||||
, fetchFromGitHub
|
||||
, buildPythonPackage
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "icnsutil";
|
||||
version = "1.0.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "relikd";
|
||||
repo = pname;
|
||||
rev = "v${version}";
|
||||
sha256 = "sha256-TfQvAbP7iCpRQg2G+ejl245NCYo9DpYwMgiwY2BuJnY=";
|
||||
};
|
||||
|
||||
doCheck = true;
|
||||
|
||||
checkPhase = ''
|
||||
${python.interpreter} tests/test_icnsutil.py
|
||||
${python.interpreter} tests/test_cli.py
|
||||
'';
|
||||
|
||||
meta = {
|
||||
homepage = "https://github.com/relikd/icnsutil";
|
||||
description = "Create and extract .icns files.";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = [ lib.maintainers.reckenrode ];
|
||||
};
|
||||
}
|
|
@ -825,8 +825,9 @@ with pkgs;
|
|||
|
||||
writeDarwinBundle = callPackage ../build-support/make-darwin-bundle/write-darwin-bundle.nix { };
|
||||
|
||||
desktopToDarwinBundle = makeSetupHook { deps = [ writeDarwinBundle imagemagick ]; }
|
||||
../build-support/setup-hooks/desktop-to-darwin-bundle.sh;
|
||||
desktopToDarwinBundle = makeSetupHook {
|
||||
deps = [ writeDarwinBundle librsvg imagemagick python3Packages.icnsutil ];
|
||||
} ../build-support/setup-hooks/desktop-to-darwin-bundle.sh;
|
||||
|
||||
keepBuildTree = makeSetupHook { } ../build-support/setup-hooks/keep-build-tree.sh;
|
||||
|
||||
|
|
|
@ -3922,6 +3922,8 @@ in {
|
|||
|
||||
icmplib = callPackage ../development/python-modules/icmplib { };
|
||||
|
||||
icnsutil = callPackage ../development/python-modules/icnsutil { };
|
||||
|
||||
ics = callPackage ../development/python-modules/ics { };
|
||||
|
||||
idasen = callPackage ../development/python-modules/idasen { };
|
||||
|
|
Loading…
Reference in a new issue