mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-07-06 17:13:24 -05:00
315 lines
9.9 KiB
Nix
315 lines
9.9 KiB
Nix
{
|
|
lib,
|
|
swiftPackages,
|
|
fetchFromGitHub,
|
|
darwin,
|
|
actool,
|
|
ibtool,
|
|
makeWrapper,
|
|
nix-update-script,
|
|
}:
|
|
|
|
let
|
|
inherit (swiftPackages) stdenv swift;
|
|
|
|
masShortcutSrc = fetchFromGitHub {
|
|
owner = "rxhanson";
|
|
repo = "MASShortcut";
|
|
rev = "2f9fbb3f959b7a683c6faaf9638d22afad37a235";
|
|
hash = "sha256-EZLt7ph24L1wwFEMlltuPutId09RBug/y9OtDhixIig=";
|
|
};
|
|
|
|
masShortcutSources = [
|
|
"Model/MASShortcut.m"
|
|
"Model/MASShortcutValidator.m"
|
|
"Monitoring/MASHotKey.m"
|
|
"Monitoring/MASShortcutMonitor.m"
|
|
"UI/MASLocalization.m"
|
|
"UI/MASShortcutView.m"
|
|
"UI/MASShortcutView+Bindings.m"
|
|
"UI/MASShortcutViewButtonCell.m"
|
|
"User Defaults Storage/MASDictionaryTransformer.m"
|
|
"User Defaults Storage/MASShortcutBinder.m"
|
|
];
|
|
|
|
# Derive object file name from source path: "UI/MASShortcutView.m" -> "MASShortcutView"
|
|
objName = src: lib.removeSuffix ".m" (baseNameOf src);
|
|
|
|
toPlist = lib.generators.toPlist { escape = true; };
|
|
|
|
# Standard macOS app plist fields. Pass app-specific attrs to override/extend.
|
|
mkAppPlist =
|
|
attrs:
|
|
toPlist (
|
|
{
|
|
CFBundleInfoDictionaryVersion = "6.0";
|
|
CFBundlePackageType = "APPL";
|
|
LSMinimumSystemVersion = "10.15";
|
|
NSMainStoryboardFile = "Main";
|
|
NSPrincipalClass = "NSApplication";
|
|
}
|
|
// attrs
|
|
);
|
|
|
|
mkFrameworkPlist =
|
|
name:
|
|
toPlist {
|
|
CFBundleExecutable = name;
|
|
CFBundleIdentifier = "com.knollsoft.Rectangle.${name}";
|
|
CFBundleInfoDictionaryVersion = "6.0";
|
|
CFBundleName = name;
|
|
CFBundlePackageType = "FMWK";
|
|
CFBundleVersion = "1";
|
|
};
|
|
|
|
# Generate shell to assemble a .app bundle: directory structure, binary,
|
|
# frameworks (with plists and optional localizations), auto-discovered IB
|
|
# resources, localizations, and Info.plist
|
|
# Requires compileIB and installLocalizations shell functions in scope
|
|
mkAppBundle =
|
|
{
|
|
name,
|
|
binary,
|
|
sourceDir,
|
|
plist,
|
|
destDir,
|
|
frameworks ? [ ],
|
|
}:
|
|
let
|
|
app = "${destDir}/${name}.app";
|
|
in
|
|
''
|
|
nixLog "assembling ${name}.app"
|
|
mkdir -p "${app}/Contents/"{MacOS,Resources}
|
|
cp "${binary}" "${app}/Contents/MacOS/${name}"
|
|
|
|
${lib.concatMapStrings (fw: ''
|
|
nixLog "installing framework: ${fw.name}"
|
|
mkdir -p "${app}/Contents/Frameworks/${fw.name}.framework/Resources"
|
|
cp "${fw.dylib}" "${app}/Contents/Frameworks/${fw.name}.framework/${fw.name}"
|
|
printf '%s' ${lib.escapeShellArg (mkFrameworkPlist fw.name)} \
|
|
> "${app}/Contents/Frameworks/${fw.name}.framework/Resources/Info.plist"
|
|
${lib.optionalString (fw ? localizations) ''
|
|
find "${fw.localizations}" -name '*.lproj' -type d \
|
|
-exec cp -r {} "${app}/Contents/Frameworks/${fw.name}.framework/Resources/" \;
|
|
''}
|
|
'') frameworks}
|
|
|
|
while IFS= read -r -d "" f; do
|
|
compileIB "$f" "${app}/Contents/Resources"
|
|
done < <(find ${sourceDir} \( -name '*.storyboard' -o -name '*.xib' \) -print0)
|
|
|
|
installLocalizations ${sourceDir} "${app}/Contents/Resources"
|
|
printf '%s' ${lib.escapeShellArg plist} > "${app}/Contents/Info.plist"
|
|
'';
|
|
|
|
mainInfoPlist =
|
|
version:
|
|
mkAppPlist {
|
|
CFBundleDevelopmentRegion = "en";
|
|
CFBundleExecutable = "Rectangle";
|
|
CFBundleIdentifier = "com.knollsoft.Rectangle";
|
|
CFBundleName = "Rectangle";
|
|
CFBundleShortVersionString = version;
|
|
CFBundleVersion = "100";
|
|
CFBundleURLTypes = [ { CFBundleURLSchemes = [ "rectangle" ]; } ];
|
|
CFBundleIconFile = "AppIcon";
|
|
CFBundleIconName = "AppIcon";
|
|
LSApplicationCategoryType = "public.app-category.productivity";
|
|
LSUIElement = true;
|
|
NSHumanReadableCopyright = "Copyright © 2019-2026 Ryan Hanson. All rights reserved.";
|
|
SUFeedURL = "https://rectangleapp.com/downloads/updates.xml";
|
|
SUPublicEDKey = "lpt9M3PhocbZ3MZiLH+crEqRfU11kfoNzGxSqiEIdvM=";
|
|
SUScheduledCheckInterval = 172800;
|
|
};
|
|
|
|
launcherInfoPlist = mkAppPlist {
|
|
CFBundleExecutable = "RectangleLauncher";
|
|
CFBundleIdentifier = "com.knollsoft.RectangleLauncher";
|
|
CFBundleName = "RectangleLauncher";
|
|
CFBundleShortVersionString = "1.0";
|
|
CFBundleVersion = "1";
|
|
LSBackgroundOnly = true;
|
|
};
|
|
in
|
|
stdenv.mkDerivation (finalAttrs: {
|
|
pname = "rectangle";
|
|
version = "0.96";
|
|
|
|
src = fetchFromGitHub {
|
|
owner = "rxhanson";
|
|
repo = "Rectangle";
|
|
tag = "v${finalAttrs.version}";
|
|
hash = "sha256-3FDCptlNztmexfFDRD/XX9fVuEHVG+EzcrqksJeI+fo=";
|
|
};
|
|
|
|
nativeBuildInputs = [
|
|
swift
|
|
actool
|
|
ibtool
|
|
darwin.autoSignDarwinBinariesHook
|
|
makeWrapper
|
|
];
|
|
|
|
dontConfigure = true;
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
buildDir="$PWD/build"
|
|
mkdir -p "$buildDir"
|
|
|
|
commonSwiftFlags=(
|
|
-O -disable-bridging-pch
|
|
-Xlinker -platform_version -Xlinker macos -Xlinker 14.0 -Xlinker 26.0
|
|
)
|
|
|
|
nixLog "building Sparkle stub framework"
|
|
swiftc "''${commonSwiftFlags[@]}" -emit-module -emit-library \
|
|
-module-name Sparkle -module-link-name Sparkle \
|
|
-emit-module-path "$buildDir/Sparkle.swiftmodule" \
|
|
-Xlinker -install_name -Xlinker "@rpath/Sparkle.framework/Sparkle" \
|
|
${./stubs/SparkleStub.swift} -o "$buildDir/libSparkle.dylib"
|
|
|
|
nixLog "building MASShortcut framework"
|
|
masDir="${masShortcutSrc}/Framework"
|
|
mkdir -p "$buildDir/MAS_headers/MASShortcut"
|
|
for h in "$masDir"/include/*.h; do
|
|
ln -sf "$h" "$buildDir/MAS_headers/MASShortcut/$(basename "$h")"
|
|
done
|
|
cp ${./stubs/MASShortcut.modulemap} "$buildDir/MAS_headers/MASShortcut/module.modulemap"
|
|
|
|
masObjFiles=()
|
|
${lib.concatMapStrings (
|
|
src:
|
|
let
|
|
name = objName src;
|
|
in
|
|
''
|
|
nixLog "compiling ${name}"
|
|
clang -fobjc-arc -O2 -I "$masDir/include" -include Foundation/Foundation.h \
|
|
-c "$masDir/${src}" -o "$buildDir/MAS_${name}.o"
|
|
masObjFiles+=("$buildDir/MAS_${name}.o")
|
|
''
|
|
) masShortcutSources}
|
|
|
|
nixLog "linking MASShortcut dylib"
|
|
clang -dynamiclib "''${masObjFiles[@]}" \
|
|
-framework AppKit -framework Carbon -framework Foundation \
|
|
-install_name "@rpath/MASShortcut.framework/MASShortcut" \
|
|
-o "$buildDir/libMASShortcut.dylib"
|
|
|
|
rectSwiftFiles=()
|
|
while IFS= read -r -d "" f; do
|
|
rectSwiftFiles+=("$f")
|
|
done < <(find Rectangle -name '*.swift' -print0)
|
|
|
|
nixLog "compiling Rectangle (''${#rectSwiftFiles[@]} swift files)"
|
|
swiftc "''${commonSwiftFlags[@]}" -emit-executable \
|
|
-module-name Rectangle \
|
|
-import-objc-header Rectangle/Rectangle-Bridging-Header.h \
|
|
-I "$buildDir" -L "$buildDir" \
|
|
-Xcc -fmodule-map-file="$buildDir/MAS_headers/MASShortcut/module.modulemap" \
|
|
-Xcc -I"$buildDir/MAS_headers" \
|
|
-lSparkle -lMASShortcut \
|
|
-framework AppKit -framework Cocoa -framework Carbon \
|
|
-framework ServiceManagement -framework IOKit \
|
|
-F /System/Library/PrivateFrameworks \
|
|
-Xlinker -rpath -Xlinker "@executable_path/../Frameworks" \
|
|
"''${rectSwiftFiles[@]}" -o "$buildDir/Rectangle"
|
|
|
|
nixLog "compiling RectangleLauncher"
|
|
swiftc "''${commonSwiftFlags[@]}" -emit-executable \
|
|
-module-name RectangleLauncher \
|
|
-parse-as-library \
|
|
-framework AppKit \
|
|
RectangleLauncher/AppDelegate.swift \
|
|
-o "$buildDir/RectangleLauncher"
|
|
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
# Compile a .storyboard or .xib into a target resource directory
|
|
# Preserves .lproj subdirectory structure automatically
|
|
compileIB() {
|
|
local src="$1" destDir="$2"
|
|
local filename="$(basename "$src")"
|
|
local name="''${filename%.*}" ext="''${filename##*.}"
|
|
local outExt; if [[ "$ext" == "storyboard" ]]; then outExt="storyboardc"; else outExt="nib"; fi
|
|
|
|
local parentDir="$(basename "$(dirname "$src")")"
|
|
nixLog "compiling IB resource: $src"
|
|
if [[ "$parentDir" == *.lproj ]]; then
|
|
mkdir -p "$destDir/$parentDir"
|
|
ibtool --compile "$destDir/$parentDir/$name.$outExt" "$src"
|
|
else
|
|
ibtool --compile "$destDir/$name.$outExt" "$src"
|
|
fi
|
|
}
|
|
|
|
# Copy .lproj dirs (for .strings localization files) and strip source IB files
|
|
installLocalizations() {
|
|
local srcDir="$1" destDir="$2"
|
|
nixLog "installing localizations from $srcDir"
|
|
find "$srcDir" -name '*.lproj' -type d -exec cp -r {} "$destDir/" \;
|
|
find "$destDir" \( -name '*.storyboard' -o -name '*.xib' \) -delete
|
|
}
|
|
|
|
# Rectangle.app
|
|
app="$out/Applications/Rectangle.app"
|
|
${mkAppBundle {
|
|
name = "Rectangle";
|
|
binary = "$buildDir/Rectangle";
|
|
sourceDir = "Rectangle";
|
|
plist = mainInfoPlist finalAttrs.version;
|
|
destDir = "$out/Applications";
|
|
frameworks = [
|
|
{
|
|
name = "Sparkle";
|
|
dylib = "$buildDir/libSparkle.dylib";
|
|
}
|
|
{
|
|
name = "MASShortcut";
|
|
dylib = "$buildDir/libMASShortcut.dylib";
|
|
localizations = "${masShortcutSrc}/Framework/Resources";
|
|
}
|
|
];
|
|
}}
|
|
|
|
# Asset catalog
|
|
nixLog "compiling asset catalog"
|
|
actool --compile "$app/Contents/Resources" \
|
|
--platform macosx --minimum-deployment-target 14.0 \
|
|
--app-icon AppIcon --output-partial-info-plist /dev/null \
|
|
Rectangle/Assets.xcassets
|
|
|
|
# RectangleLauncher.app
|
|
${mkAppBundle {
|
|
name = "RectangleLauncher";
|
|
binary = "$buildDir/RectangleLauncher";
|
|
sourceDir = "RectangleLauncher";
|
|
plist = launcherInfoPlist;
|
|
destDir = "$app/Contents/Library/LoginItems";
|
|
}}
|
|
|
|
makeWrapper "$app/Contents/MacOS/Rectangle" "$out/bin/rectangle"
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
passthru.updateScript = nix-update-script { };
|
|
|
|
meta = {
|
|
description = "Move and resize windows in macOS using keyboard shortcuts or snap areas";
|
|
homepage = "https://rectangleapp.com/";
|
|
license = lib.licenses.mit;
|
|
maintainers = with lib.maintainers; [
|
|
_4evy
|
|
Intuinewin
|
|
wegank
|
|
];
|
|
platforms = lib.platforms.darwin;
|
|
};
|
|
})
|