mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-07-19 15:11:30 +00:00
itsycal: build from source (#506430)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
From 9ce5e368c00ed887f6d2628d4e8eff664bff5522 Mon Sep 17 00:00:00 2001
|
||||
From: 4evy <git@evy.pink>
|
||||
Date: Sat, 11 Jul 2026 01:12:36 +0300
|
||||
Subject: [PATCH 1/4] Allow running from the Nix store
|
||||
|
||||
Upstream normally requires the bundle to live in /Applications because its
|
||||
Sparkle updater needs a stable, writable installation path. Nix applications
|
||||
instead live in the immutable Nix store and are updated by Nix.
|
||||
|
||||
Use upstream's existing kAllowOutsideApplicationsFolder escape hatch as the
|
||||
registered default. This preserves upstream's check and keeps the downstream
|
||||
change to one explicit policy setting.
|
||||
---
|
||||
Itsycal/AppDelegate.m | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/Itsycal/AppDelegate.m b/Itsycal/AppDelegate.m
|
||||
index 5cc187a..ee256d9 100644
|
||||
--- a/Itsycal/AppDelegate.m
|
||||
+++ b/Itsycal/AppDelegate.m
|
||||
@@ -42,6 +42,8 @@
|
||||
kThemePreference: @0, // System=0, Light=1, Dark=2
|
||||
kHideIcon: @(NO),
|
||||
kShowLocation: @(NO),
|
||||
+ // Nix installs the app in the immutable Nix store, not /Applications.
|
||||
+ kAllowOutsideApplicationsFolder: @(YES),
|
||||
kEnableTahoeMenuIcons: @(NO),
|
||||
kDoNotDrawOutlineAroundCurrentMonth: @(NO)
|
||||
}];
|
||||
@@ -0,0 +1,124 @@
|
||||
From f1474c9b296a3fbef30ca577dbd9a92224290b97 Mon Sep 17 00:00:00 2001
|
||||
From: 4evy <git@evy.pink>
|
||||
Date: Sat, 11 Jul 2026 01:12:47 +0300
|
||||
Subject: [PATCH 2/4] Remove self-update controls for Nix builds
|
||||
|
||||
Sparkle cannot replace an application inside the immutable Nix store. Leaving
|
||||
its menu item and preference checkbox backed by a no-op stub is misleading.
|
||||
|
||||
Remove the updater object, imports, controls, action, and matching Tahoe menu
|
||||
symbol so the Nix build has no Sparkle runtime dependency or non-functional
|
||||
update UI.
|
||||
---
|
||||
Itsycal/Base.lproj/MainMenu.xib | 1 -
|
||||
Itsycal/PrefsGeneralVC.m | 11 ++---------
|
||||
Itsycal/ViewController.m | 9 ---------
|
||||
3 files changed, 2 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/Itsycal/Base.lproj/MainMenu.xib b/Itsycal/Base.lproj/MainMenu.xib
|
||||
index aa2067e..1a25048 100644
|
||||
--- a/Itsycal/Base.lproj/MainMenu.xib
|
||||
+++ b/Itsycal/Base.lproj/MainMenu.xib
|
||||
@@ -89,6 +89,5 @@
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
- <customObject id="9Zs-J5-87v" customClass="SUUpdater"/>
|
||||
</objects>
|
||||
</document>
|
||||
diff --git a/Itsycal/PrefsGeneralVC.m b/Itsycal/PrefsGeneralVC.m
|
||||
index 7c19942..86f6be0 100644
|
||||
--- a/Itsycal/PrefsGeneralVC.m
|
||||
+++ b/Itsycal/PrefsGeneralVC.m
|
||||
@@ -9,7 +9,6 @@
|
||||
#import "MoVFLHelper.h"
|
||||
#import "EventCenter.h"
|
||||
#import "MASShortcut/Shortcut.h"
|
||||
-#import "Sparkle/SUUpdater.h"
|
||||
|
||||
static NSString * const kSourceCellId = @"SourceCell";
|
||||
static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
@@ -33,7 +32,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
{
|
||||
NSTextField *_title;
|
||||
NSButton *_login;
|
||||
- NSButton *_checkUpdates;
|
||||
NSButton *_beepBeep;
|
||||
NSPopUpButton *_firstDayPopup;
|
||||
NSTableView *_calendarsTV;
|
||||
@@ -66,7 +64,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
// Checkboxes
|
||||
_login = chkbx(NSLocalizedString(@"Launch at login", @""));
|
||||
_login.action = @selector(launchAtLogin:);
|
||||
- _checkUpdates = chkbx(NSLocalizedString(@"Automatically check for updates", @""));
|
||||
_beepBeep = chkbx(NSLocalizedString(@"Beep beep on the hour", @""));
|
||||
|
||||
// First day of week label
|
||||
@@ -130,10 +127,9 @@ static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
NSLocalizedString(@"31 days", @"")]];
|
||||
[v addSubview:_agendaDaysPopup];
|
||||
|
||||
- MoVFLHelper *vfl = [[MoVFLHelper alloc] initWithSuperview:v metrics:@{@"m": @20} views:NSDictionaryOfVariableBindings(_login, _checkUpdates, _beepBeep, firstDayLabel, _firstDayPopup, shortcutLabel, shortcutView, tvContainer, agendaDaysLabel, _agendaDaysPopup)];
|
||||
- [vfl :@"V:|-m-[_login]-[_checkUpdates]-[_beepBeep]-20-[_firstDayPopup]-20-[shortcutLabel]-3-[shortcutView(25)]-20-[tvContainer(170)]-[_agendaDaysPopup]-m-|"];
|
||||
+ MoVFLHelper *vfl = [[MoVFLHelper alloc] initWithSuperview:v metrics:@{@"m": @20} views:NSDictionaryOfVariableBindings(_login, _beepBeep, firstDayLabel, _firstDayPopup, shortcutLabel, shortcutView, tvContainer, agendaDaysLabel, _agendaDaysPopup)];
|
||||
+ [vfl :@"V:|-m-[_login]-[_beepBeep]-20-[_firstDayPopup]-20-[shortcutLabel]-3-[shortcutView(25)]-20-[tvContainer(170)]-[_agendaDaysPopup]-m-|"];
|
||||
[vfl :@"H:|-m-[_login]-(>=m)-|"];
|
||||
- [vfl :@"H:|-m-[_checkUpdates]-(>=m)-|"];
|
||||
[vfl :@"H:|-m-[_beepBeep]-(>=m)-|"];
|
||||
[vfl :@"H:|-m-[firstDayLabel]-[_firstDayPopup]-(>=m)-|" :NSLayoutFormatAlignAllFirstBaseline];
|
||||
[vfl :@"H:|-(>=m)-[shortcutLabel]-(>=m)-|"];
|
||||
@@ -144,9 +140,6 @@ static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
// Center shortcutLabel
|
||||
[v addConstraint:[NSLayoutConstraint constraintWithItem:shortcutLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:v attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
|
||||
- // Binding for Sparkle automatic update checks
|
||||
- [_checkUpdates bind:@"value" toObject:[SUUpdater sharedUpdater] withKeyPath:@"automaticallyChecksForUpdates" options:@{NSContinuouslyUpdatesValueBindingOption: @(YES)}];
|
||||
-
|
||||
// Binding for hourly beep
|
||||
[_beepBeep bind:@"value" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:[@"values." stringByAppendingString:kBeepBeepOnTheHour] options:@{NSContinuouslyUpdatesValueBindingOption: @(YES)}];
|
||||
|
||||
diff --git a/Itsycal/ViewController.m b/Itsycal/ViewController.m
|
||||
index 1d55c4f..0bd303b 100644
|
||||
--- a/Itsycal/ViewController.m
|
||||
+++ b/Itsycal/ViewController.m
|
||||
@@ -21,7 +21,6 @@
|
||||
#import "MoButton.h"
|
||||
#import "MoVFLHelper.h"
|
||||
#import "MoUtils.h"
|
||||
-#import "Sparkle/SUUpdater.h"
|
||||
|
||||
@implementation ViewController
|
||||
{
|
||||
@@ -378,7 +377,6 @@
|
||||
NSInteger i = 0;
|
||||
|
||||
[optMenu insertItemWithTitle:NSLocalizedString(@"About Itsycal", @"") action:@selector(showAbout:) keyEquivalent:@"" atIndex:i++];
|
||||
- [optMenu insertItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdates:) keyEquivalent:@"" atIndex:i++];
|
||||
[optMenu insertItem:[NSMenuItem separatorItem] atIndex:i++];
|
||||
[optMenu insertItemWithTitle:NSLocalizedString(@"Go to Date…", @"") action:@selector(showDatePickerPopover:) keyEquivalent:@"T" atIndex:i++];
|
||||
[optMenu insertItem:[NSMenuItem separatorItem] atIndex:i++];
|
||||
@@ -394,7 +392,6 @@
|
||||
NSInteger index = 0;
|
||||
NSArray *symbolNames = @[
|
||||
@"info.circle",
|
||||
- @"arrow.trianglehead.2.clockwise",
|
||||
@"21.calendar",
|
||||
@"gear",
|
||||
@"calendar.badge.clock",
|
||||
@@ -462,11 +459,6 @@
|
||||
[self.prefsWC showWindow:self];
|
||||
}
|
||||
|
||||
-- (void)checkForUpdates:(id)sender
|
||||
-{
|
||||
- [[SUUpdater sharedUpdater] checkForUpdates:self];
|
||||
-}
|
||||
-
|
||||
- (void)openDateAndTimePrefs:(id)sender
|
||||
{
|
||||
NSURL *url = nil;
|
||||
@@ -1514,4 +1506,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
-
|
||||
@@ -0,0 +1,116 @@
|
||||
From d3d2dd9ce8b566496503c38158199432a5ae87bb Mon Sep 17 00:00:00 2001
|
||||
From: 4evy <git@evy.pink>
|
||||
Date: Sat, 11 Jul 2026 01:12:47 +0300
|
||||
Subject: [PATCH 3/4] Use modern ServiceManagement login items
|
||||
|
||||
LSSharedFileList was deprecated in macOS 10.11 and its status lookup can fail
|
||||
when disconnected network volumes are present. Nixpkgs now targets macOS 14,
|
||||
so no legacy fallback is needed.
|
||||
|
||||
Use SMAppService.mainAppService, available since macOS 13. Treat
|
||||
RequiresApproval as registered so users can still turn the pending login item
|
||||
off. The package signs the complete app bundle after fixup, as required by
|
||||
ServiceManagement.
|
||||
---
|
||||
Itsycal/MoLoginItem.m | 58 +++++++---------------------------------
|
||||
Itsycal/PrefsGeneralVC.m | 12 +--------
|
||||
2 files changed, 11 insertions(+), 59 deletions(-)
|
||||
|
||||
diff --git a/Itsycal/MoLoginItem.m b/Itsycal/MoLoginItem.m
|
||||
index 7efa602..0dc4960 100644
|
||||
--- a/Itsycal/MoLoginItem.m
|
||||
+++ b/Itsycal/MoLoginItem.m
|
||||
@@ -4,60 +4,22 @@
|
||||
//
|
||||
|
||||
#import "MoLoginItem.h"
|
||||
-
|
||||
-// LSSharedFileList API was deprecated in macOS 10.11
|
||||
-#pragma clang diagnostic push
|
||||
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+#import <ServiceManagement/ServiceManagement.h>
|
||||
|
||||
BOOL MOIsLoginItemEnabled(void)
|
||||
{
|
||||
- BOOL isEnabled = NO;
|
||||
- NSString *appPath = [[NSBundle mainBundle] bundlePath];
|
||||
- LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
|
||||
-
|
||||
- if (loginItemsRef) {
|
||||
- UInt32 seedValue;
|
||||
- NSArray *loginItems = CFBridgingRelease(LSSharedFileListCopySnapshot(loginItemsRef, &seedValue));
|
||||
- for (id item in loginItems) {
|
||||
- LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
|
||||
- NSURL *pathURL = CFBridgingRelease(LSSharedFileListItemCopyResolvedURL(itemRef, 0, NULL));
|
||||
- if (pathURL && [pathURL.path hasPrefix:appPath]) {
|
||||
- isEnabled = YES;
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- CFRelease(loginItemsRef);
|
||||
- }
|
||||
- return isEnabled;
|
||||
+ SMAppServiceStatus status = SMAppService.mainAppService.status;
|
||||
+ return status == SMAppServiceStatusEnabled || status == SMAppServiceStatusRequiresApproval;
|
||||
}
|
||||
|
||||
void MOEnableLoginItem(BOOL enable)
|
||||
{
|
||||
- NSString *appPath = [[NSBundle mainBundle] bundlePath];
|
||||
- LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
|
||||
-
|
||||
- if (loginItemsRef) {
|
||||
- if (enable) {
|
||||
- // We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list.
|
||||
- CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
|
||||
- LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
|
||||
- if (item != NULL) CFRelease(item);
|
||||
- }
|
||||
- else {
|
||||
- // Grab the contents of the shared file list (LSSharedFileListItemRef objects)
|
||||
- // and pop it in an array so we can iterate through it to find our item.
|
||||
- UInt32 seedValue;
|
||||
- NSArray *loginItems = CFBridgingRelease(LSSharedFileListCopySnapshot(loginItemsRef, &seedValue));
|
||||
- for (id item in loginItems) {
|
||||
- LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
|
||||
- NSURL *pathURL = CFBridgingRelease(LSSharedFileListItemCopyResolvedURL(itemRef, 0, NULL));
|
||||
- if (pathURL && [pathURL.path hasPrefix:appPath]) {
|
||||
- LSSharedFileListItemRemove(loginItemsRef, itemRef); // Deleting the item
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
- CFRelease(loginItemsRef);
|
||||
+ SMAppService *service = SMAppService.mainAppService;
|
||||
+ NSError *error = nil;
|
||||
+ BOOL succeeded = enable
|
||||
+ ? [service registerAndReturnError:&error]
|
||||
+ : [service unregisterAndReturnError:&error];
|
||||
+ if (!succeeded) {
|
||||
+ NSLog(@"Unable to %@ launch at login: %@", enable ? @"enable" : @"disable", error);
|
||||
}
|
||||
}
|
||||
-
|
||||
-#pragma clang diagnostic pop
|
||||
diff --git a/Itsycal/PrefsGeneralVC.m b/Itsycal/PrefsGeneralVC.m
|
||||
index 86f6be0..ecfd30d 100644
|
||||
--- a/Itsycal/PrefsGeneralVC.m
|
||||
+++ b/Itsycal/PrefsGeneralVC.m
|
||||
@@ -158,17 +158,7 @@ static NSString * const kCalendarCellId = @"CalendarCell";
|
||||
|
||||
_sourcesAndCalendars = [self.ec sourcesAndCalendars];
|
||||
|
||||
- // The API used to check the login item's state (LSSharedFileList) causes
|
||||
- // errors for users who have network drives but are not connected to their
|
||||
- // network (github.com/sfsam/Itsycal/issues/15). Give them an option to
|
||||
- // disable this check.
|
||||
- if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DoNotCheckLoginItemStatus"] == NO) {
|
||||
- _login.hidden = NO;
|
||||
- _login.state = MOIsLoginItemEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
- }
|
||||
- else {
|
||||
- _login.hidden = YES;
|
||||
- }
|
||||
+ _login.state = MOIsLoginItemEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
|
||||
_calendarsTV.enabled = self.ec.calendarAccessGranted;
|
||||
_agendaDaysPopup.enabled = self.ec.calendarAccessGranted;
|
||||
@@ -0,0 +1,90 @@
|
||||
From 6fb6ac13a8deee492449442e1ab3d7429aa9fff8 Mon Sep 17 00:00:00 2001
|
||||
From: 4evy <git@evy.pink>
|
||||
Date: Sat, 11 Jul 2026 01:12:55 +0300
|
||||
Subject: [PATCH 4/4] Use APIs available on the Nixpkgs macOS baseline
|
||||
|
||||
The package metadata and Mach-O deployment target are both macOS 14. Remove
|
||||
compatibility branches for older systems and use the corresponding current
|
||||
APIs:
|
||||
|
||||
* request EventKit full access directly;
|
||||
* discover the calendar handler through NSWorkspace and
|
||||
UniformTypeIdentifiers;
|
||||
* use the renamed CoreAudio main-element constant.
|
||||
---
|
||||
Itsycal/EventCenter.m | 20 ++------------------
|
||||
Itsycal/ViewController.m | 9 ++++-----
|
||||
2 files changed, 6 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/Itsycal/EventCenter.m b/Itsycal/EventCenter.m
|
||||
index 148c631..3862630 100644
|
||||
--- a/Itsycal/EventCenter.m
|
||||
+++ b/Itsycal/EventCenter.m
|
||||
@@ -59,15 +59,7 @@ static NSString *kSelectedCalendars = @"SelectedCalendars";
|
||||
_queueIsol = dispatch_queue_create("com.mowglii.Itsycal.queueIsol", DISPATCH_QUEUE_SERIAL);
|
||||
_queueIsol2 = dispatch_queue_create("com.mowglii.Itsycal.queueIsol2", DISPATCH_QUEUE_SERIAL);
|
||||
_store = [EKEventStore new];
|
||||
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
||||
- if (@available(macOS 14.0, *)) {
|
||||
- [_store requestFullAccessToEventsWithCompletion:requestCompletionHandler];
|
||||
- } else {
|
||||
- [_store requestAccessToEntityType:EKEntityTypeEvent completion:requestCompletionHandler];
|
||||
- }
|
||||
-#else
|
||||
- [_store requestAccessToEntityType:EKEntityTypeEvent completion:requestCompletionHandler];
|
||||
-#endif
|
||||
+ [_store requestFullAccessToEventsWithCompletion:requestCompletionHandler];
|
||||
|
||||
// Refetch everything when the event store has changed.
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
@@ -85,15 +77,7 @@ static NSString *kSelectedCalendars = @"SelectedCalendars";
|
||||
#pragma mark - Public (main thread)
|
||||
|
||||
- (BOOL)calendarAccessGranted {
|
||||
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
||||
- if (@available(macOS 14.0, *)) {
|
||||
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusFullAccess;
|
||||
- } else {
|
||||
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusAuthorized;
|
||||
- }
|
||||
-#else
|
||||
- return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusAuthorized;
|
||||
-#endif
|
||||
+ return [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent] == EKAuthorizationStatusFullAccess;
|
||||
}
|
||||
|
||||
- (NSString *)defaultCalendarIdentifier {
|
||||
diff --git a/Itsycal/ViewController.m b/Itsycal/ViewController.m
|
||||
index 0bd303b..a887682 100644
|
||||
--- a/Itsycal/ViewController.m
|
||||
+++ b/Itsycal/ViewController.m
|
||||
@@ -21,6 +21,7 @@
|
||||
#import "MoButton.h"
|
||||
#import "MoVFLHelper.h"
|
||||
#import "MoUtils.h"
|
||||
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
@implementation ViewController
|
||||
{
|
||||
@@ -319,10 +320,8 @@
|
||||
// Determine the default calendar app.
|
||||
// See: support.busymac.com/help/21535-busycal-url-handler
|
||||
|
||||
- CFStringRef strRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, CFSTR("ics"), kUTTypeData);
|
||||
- CFStringRef bundleID = LSCopyDefaultRoleHandlerForContentType(strRef, kLSRolesEditor);
|
||||
- CFRelease(strRef);
|
||||
- NSString *defaultCalendarAppBundleID = CFBridgingRelease(bundleID);
|
||||
+ NSURL *defaultCalendarAppURL = [NSWorkspace.sharedWorkspace URLForApplicationToOpenContentType:UTTypeCalendarEvent];
|
||||
+ NSString *defaultCalendarAppBundleID = [NSBundle bundleWithURL:defaultCalendarAppURL].bundleIdentifier;
|
||||
|
||||
// Use URL scheme to open BusyCal or Fantastical2 on the
|
||||
// date selected in our calendar.
|
||||
@@ -1263,7 +1262,7 @@
|
||||
AudioObjectPropertyAddress propertyAddress;
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
- propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
+ propertyAddress.mElement = kAudioObjectPropertyElementMain;
|
||||
|
||||
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, &deviceID);
|
||||
|
||||
@@ -1,42 +1,240 @@
|
||||
{
|
||||
lib,
|
||||
fetchzip,
|
||||
stdenvNoCC,
|
||||
stdenv,
|
||||
fetchFromGitHub,
|
||||
actool,
|
||||
ibtool,
|
||||
lld,
|
||||
makeWrapper,
|
||||
rcodesign,
|
||||
re-plistbuddy,
|
||||
nix-update-script,
|
||||
}:
|
||||
|
||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||
let
|
||||
# Upstream ships MASShortcut only as a prebuilt framework. Build the
|
||||
# maintained Rectangle fork instead; it adds F20 and macOS Tahoe fixes while
|
||||
# retaining the API and bundle identity used by Itsycal.
|
||||
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"
|
||||
];
|
||||
|
||||
masShortcutInfoPlist = lib.generators.toPlist { escape = true; } {
|
||||
CFBundleDevelopmentRegion = "English";
|
||||
CFBundleExecutable = "MASShortcut";
|
||||
CFBundleIdentifier = "com.github.shpakovski.MASShortcut";
|
||||
CFBundleInfoDictionaryVersion = "6.0";
|
||||
CFBundleName = "MASShortcut";
|
||||
CFBundlePackageType = "FMWK";
|
||||
CFBundleShortVersionString = "2.4.0";
|
||||
CFBundleVersion = "2.4.0";
|
||||
NSHumanReadableCopyright = "Copyright © Vadim Shpakovski. All rights reserved.";
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "itsycal";
|
||||
version = "0.15.12";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://itsycal.s3.amazonaws.com/Itsycal-${finalAttrs.version}.zip";
|
||||
hash = "sha256-2Xu1ZQnNl0o2/AYOIjxZPDnc0TxMXrqKej7CCZEVV9I=";
|
||||
stripRoot = false;
|
||||
src = fetchFromGitHub {
|
||||
owner = "sfsam";
|
||||
repo = "Itsycal";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-K25oG8d+OauNHLkatLLskfcWCqOmM2WEw2Ygd3v1uqE=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# Use upstream's hidden escape hatch for installations outside /Applications.
|
||||
./0001-Allow-running-from-the-Nix-store.patch
|
||||
# Nix owns updates, so remove the otherwise non-functional Sparkle UI.
|
||||
./0002-Remove-self-update-controls-for-Nix-builds.patch
|
||||
# Replace the deprecated LSSharedFileList implementation with SMAppService.
|
||||
./0003-Use-modern-ServiceManagement-login-items.patch
|
||||
# The Nixpkgs Darwin baseline is macOS 14; use its non-deprecated APIs.
|
||||
./0004-Use-APIs-available-on-the-Nixpkgs-macOS-baseline.patch
|
||||
];
|
||||
|
||||
strictDeps = true;
|
||||
|
||||
nativeBuildInputs = [
|
||||
actool
|
||||
ibtool
|
||||
lld
|
||||
makeWrapper
|
||||
rcodesign
|
||||
re-plistbuddy
|
||||
];
|
||||
|
||||
# The classic open-source ld64 crashes while linking MASShortcut on arm64.
|
||||
# Keep lld until the cctools linker can link this framework reliably.
|
||||
env.NIX_CFLAGS_LINK = "-fuse-ld=lld";
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
postPatch = ''
|
||||
# Both upstream frameworks contain native code. MASShortcut is rebuilt
|
||||
# below and Sparkle is removed by patch 0002, so make accidental reuse fail.
|
||||
rm -rf Itsycal/_frameworks
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
buildDir="$PWD/build"
|
||||
mkdir -p "$buildDir/module-cache"
|
||||
|
||||
# actool accepts one catalog, while upstream keeps images and colors in
|
||||
# separate catalogs. Merge them without changing their contents.
|
||||
cp -R Itsycal/Images.xcassets "$buildDir/Assets.xcassets"
|
||||
cp -R Itsycal/Colors.xcassets/*.colorset "$buildDir/Assets.xcassets/"
|
||||
|
||||
# Compile .m -> .o; flags before --, sources after. Sets _objects array
|
||||
compile_objc() {
|
||||
local prefix="$1"; shift
|
||||
local -a flags=()
|
||||
while [[ "$1" != "--" ]]; do flags+=("$1"); shift; done; shift
|
||||
_objects=()
|
||||
for src in "$@"; do
|
||||
local obj="$buildDir/''${prefix}_$(basename "$src" .m).o"
|
||||
clang -fobjc-arc "''${flags[@]}" -c "$src" -o "$obj"
|
||||
_objects+=("$obj")
|
||||
done
|
||||
}
|
||||
|
||||
link_dylib() {
|
||||
local name="$1"; shift
|
||||
clang -dynamiclib -fobjc-arc \
|
||||
-install_name "@rpath/$name.framework/$name" "$@"
|
||||
}
|
||||
|
||||
# Build MASShortcut as a real framework. The explicit source list avoids
|
||||
# pulling its demo or test targets into the application.
|
||||
masHeaders="$buildDir/MAS_headers/MASShortcut"
|
||||
mkdir -p "$masHeaders"
|
||||
cp ${masShortcutSrc}/Framework/include/*.h "$masHeaders/"
|
||||
|
||||
masSources=( ${
|
||||
lib.escapeShellArgs (map (src: "${masShortcutSrc}/Framework/${src}") masShortcutSources)
|
||||
} )
|
||||
compile_objc mas -fmodules -fmodules-cache-path="$buildDir/module-cache" \
|
||||
-I"$masHeaders" -include AppKit/AppKit.h -- "''${masSources[@]}"
|
||||
link_dylib MASShortcut -framework AppKit -framework Carbon \
|
||||
-compatibility_version 1 -current_version 2.4.0 \
|
||||
-o "$buildDir/MASShortcut.dylib" "''${_objects[@]}"
|
||||
|
||||
# Itsycal
|
||||
compile_objc app -fmodules -fmodules-cache-path="$buildDir/module-cache" \
|
||||
-I"$buildDir/MAS_headers" -IItsycal -- Itsycal/*.m
|
||||
clang -fobjc-arc \
|
||||
-framework AppKit -framework EventKit -framework ScriptingBridge \
|
||||
-framework Carbon -framework ServiceManagement -framework UniformTypeIdentifiers \
|
||||
-Wl,-rpath,@executable_path/../Frameworks \
|
||||
"$buildDir/MASShortcut.dylib" \
|
||||
-o "$buildDir/Itsycal" "''${_objects[@]}"
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p "$out/Applications"
|
||||
cp -R Itsycal.app "$out/Applications/"
|
||||
appDir="$out/Applications/Itsycal.app/Contents"
|
||||
mkdir -p "$appDir"/{MacOS,Frameworks,Resources}
|
||||
|
||||
cp "$buildDir/Itsycal" "$appDir/MacOS/Itsycal"
|
||||
|
||||
# Assemble the framework bundle metadata normally generated by Xcode.
|
||||
mkdir -p "$appDir/Frameworks/MASShortcut.framework/Resources"
|
||||
cp "$buildDir/MASShortcut.dylib" "$appDir/Frameworks/MASShortcut.framework/MASShortcut"
|
||||
cp -r ${masShortcutSrc}/Framework/Resources/*.lproj "$appDir/Frameworks/MASShortcut.framework/Resources/"
|
||||
printf '%s' ${lib.escapeShellArg masShortcutInfoPlist} \
|
||||
> "$appDir/Frameworks/MASShortcut.framework/Resources/Info.plist"
|
||||
|
||||
# Resources
|
||||
ibtool --compile "$appDir/Resources/MainMenu.nib" Itsycal/Base.lproj/MainMenu.xib
|
||||
actool --compile "$appDir/Resources" \
|
||||
--platform macosx \
|
||||
--minimum-deployment-target ${stdenv.hostPlatform.darwinMinVersion} \
|
||||
--app-icon AppIcon \
|
||||
--output-partial-info-plist "$buildDir/asset-info.plist" \
|
||||
"$buildDir/Assets.xcassets"
|
||||
cp Itsycal/beep.mp3 "$appDir/Resources/"
|
||||
mkdir -p "$appDir/Resources/Fonts"
|
||||
cp Itsycal/_fonts/Mow.otf "$appDir/Resources/Fonts/"
|
||||
for lproj in Itsycal/*.lproj; do
|
||||
mkdir -p "$appDir/Resources/$(basename "$lproj")"
|
||||
cp "$lproj"/*.strings "$appDir/Resources/$(basename "$lproj")/" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Resolve the Xcode variables in upstream's plist. Keeping that plist as
|
||||
# the source of truth means new privacy and bundle metadata is preserved.
|
||||
bundleVersion=$(sed -n 's/.*CURRENT_PROJECT_VERSION = \([0-9]*\);/\1/p' \
|
||||
Itsycal.xcodeproj/project.pbxproj | head -1)
|
||||
cp Itsycal/Info.plist "$appDir/Info.plist"
|
||||
substituteInPlace "$appDir/Info.plist" \
|
||||
--replace-fail '$(EXECUTABLE_NAME)' 'Itsycal' \
|
||||
--replace-fail '$(PRODUCT_BUNDLE_IDENTIFIER)' 'com.mowglii.ItsycalApp' \
|
||||
--replace-fail '$(PRODUCT_NAME)' 'Itsycal' \
|
||||
--replace-fail '$(MARKETING_VERSION)' '${finalAttrs.version}' \
|
||||
--replace-fail '$(CURRENT_PROJECT_VERSION)' "$bundleVersion" \
|
||||
--replace-fail '$(MACOSX_DEPLOYMENT_TARGET)' '${stdenv.hostPlatform.darwinMinVersion}'
|
||||
|
||||
# Merge actool's icon metadata and remove the Sparkle configuration that
|
||||
# no longer has a consumer in this immutable Nix build.
|
||||
PlistBuddy -c 'Set :CFBundleIconFile AppIcon' \
|
||||
-c 'Add :CFBundleIconName string AppIcon' \
|
||||
-c 'Delete :SUAllowsAutomaticUpdates' \
|
||||
-c 'Delete :SUEnableAutomaticChecks' \
|
||||
-c 'Delete :SUFeedURL' \
|
||||
-c 'Delete :SUPublicEDKey' \
|
||||
-c 'Delete :SUScheduledCheckInterval' \
|
||||
"$appDir/Info.plist"
|
||||
|
||||
makeWrapper "$out/Applications/Itsycal.app/Contents/MacOS/Itsycal" "$out/bin/itsycal"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
updateScript = ./update.sh;
|
||||
# Signing only Mach-O files leaves app resources unsealed. Sign the complete
|
||||
# bundle after fixup so ServiceManagement accepts it and nested code verifies.
|
||||
postFixup = ''
|
||||
rcodesign sign "$out/Applications/Itsycal.app"
|
||||
'';
|
||||
|
||||
passthru.updateScript = nix-update-script {
|
||||
# Ignore upstream's non-version "help" tag.
|
||||
extraArgs = [ "--version-regex=^([0-9]+\\.[0-9]+\\.[0-9]+)$" ];
|
||||
};
|
||||
|
||||
meta = {
|
||||
changelog = "https://www.mowglii.com/itsycal/versionhistory.html";
|
||||
description = "Tiny menu bar calendar";
|
||||
homepage = "https://www.mowglii.com/itsycal/";
|
||||
license = lib.licenses.mit;
|
||||
license = with lib.licenses; [
|
||||
bsd2
|
||||
mit
|
||||
];
|
||||
mainProgram = "itsycal";
|
||||
maintainers = with lib.maintainers; [
|
||||
eclairevoyant
|
||||
_4evy
|
||||
];
|
||||
platforms = lib.platforms.darwin;
|
||||
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
|
||||
sourceProvenance = with lib.sourceTypes; [ fromSource ];
|
||||
};
|
||||
})
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p curl xq-xml common-updater-scripts
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT="$(dirname "$(readlink -f "$0")")"
|
||||
NIX_DRV="$ROOT/package.nix"
|
||||
if [ ! -f "$NIX_DRV" ]; then
|
||||
echo "ERROR: cannot find package.nix in $ROOT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LATEST_VERSION="$(curl -Ls https://www.mowglii.com/itsycal/versionhistory.html | xq -m -q 'h4' -a 'id' | head -n1)"
|
||||
|
||||
if [ -z "$LATEST_VERSION" ]; then
|
||||
echo "ERROR: Failed to scrape the latest version."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
update-source-version itsycal "$LATEST_VERSION" --file="$NIX_DRV"
|
||||
Reference in New Issue
Block a user