Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions CHANGES-macOS-modernization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# SaveHollywood — macOS 26/27 modernization

Changes made against `packagesdev/savehollywood` master (v2.6, last upstream activity 2018)
to make the screen saver build and run correctly on macOS 27 with Xcode 27.
Tested on macOS 27.0 beta (25A5388g), Xcode 27.0 beta (27A5228h), Apple Silicon.

A machine-readable diff of every change is in `macos27-changes.patch` (`diff -ru` against
upstream master).

---

## 1. Build configuration (`SaveHollywood.xcodeproj/project.pbxproj`)

| Setting | Before | After | Why |
|---|---|---|---|
| `SDKROOT` (project level) | `macosx10.9` | `macosx` | The 10.9 SDK no longer exists; pinned SDK breaks the build immediately. |
| `MACOSX_DEPLOYMENT_TARGET` | `10.9` | `12.0` | Xcode 27 rejects deployment targets below 12.0. |
| `ARCHS` | `$(ARCHS_STANDARD_64_BIT)` | `$(ARCHS_STANDARD)` | Produces arm64. macOS 27 dropped Intel support; an x86_64-only `.saver` will not load. |
| Framework file references | absolute paths into `MacOSX10.14.sdk` | SDK-relative (`sourceTree = SDKROOT`) | The 10.14 SDK paths are stale; SDK-relative references track the active SDK. |
| `OTHER_LDFLAGS` | — | `-framework UniformTypeIdentifiers` | Needed for the `UTType`-based open-panel filter (see §3.1). |

## 2. Deprecated API modernization (mechanical, no behavior change)

* `NSOnState` / `NSOffState` → `NSControlStateValueOn` / `NSControlStateValueOff`
* `NSCompositeSourceOver` → `NSCompositingOperationSourceOver`
* `NSCenterTextAlignment` → `NSTextAlignmentCenter`
* `NSRegularControlSize` → `NSControlSizeRegular`
* `NSOKButton` / `NSFileHandlingPanelOKButton` → `NSModalResponseOK`
* `NSCalendarDate`/`yearOfCommonEra` → `NSCalendar component:fromDate:` (`SHAboutBoxWindowController.m`)
* `colorUsingColorSpaceName:` → `colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]` (`NSColor+String.m`)

## 3. Functional fixes for modern macOS

### 3.1 Folder selection broken in the open panel (`SHConfigurationWindowController.m`)

`-[NSOpenPanel setAllowedFileTypes:]` with `[AVURLAsset audiovisualTypes]` now disables
directories in the panel even with `canChooseDirectories = YES` (older macOS ignored the
filter for folders). Replaced with `allowedContentTypes` built from `UTType` objects and
explicitly including `UTTypeFolder`. This is why the UniformTypeIdentifiers framework is
now linked.

### 3.2 Drag & drop onto the assets table (`SHConfigurationWindowController.m`)

`NSFilenamesPboardType` (deprecated 10.14) replaced with `NSPasteboardTypeFileURL`.
The two `propertyListForType:` reads were replaced with
`readObjectsForClasses:@[[NSURL class]]` + `NSPasteboardURLReadingFileURLsOnlyKey`,
mapped back to paths, so the downstream path-based logic is unchanged.

### 3.3 Configure sheet would not dismiss (System Settings had to be force-quit)

`[NSApp endSheet:]` no longer dismisses a configure sheet hosted by System Settings.
`closeDialog:` now uses `[self.window.sheetParent endSheet:returnCode:]` when a sheet
parent exists, falling back to the legacy call otherwise.

### 3.4 Saver never stops after unlock (macOS 14+ `legacyScreenSaver` bug)

On macOS 14 and later the system frequently never calls `stopAnimation` on legacy screen
savers after the user unlocks; `legacyScreenSaver` keeps running and audio keeps playing
indefinitely. Workaround (same approach the Aerial project uses): on `startAnimation`
(non-preview only) subscribe to the distributed notification `com.apple.screenIsUnlocked`;
on receipt, run `stopAnimation` (which also persists the resume position) and `exit(0)`
the host process. The system respawns `legacyScreenSaver` on demand.

### 3.5 Preview instances play audio and stack up (`SaveHollywoodView.m`)

The modern host can pass `isPreview == NO` for the small System Settings preview, and it
instantiates a new saver view per settings-pane visit without stopping the old one. Result:
multiple simultaneous audio streams. Two defenses:

* Preview detection no longer trusts the flag alone: a frame ≤ 400×400 pt is treated as a
preview. Previews are always muted and never touch persisted state.
* `viewDidMoveToWindow` pauses the player whenever the view is detached from its window,
so leaked instances go silent even if the host never stops them.

### 3.6 Resume-from-last-position broken (`SaveHollywoodView.m`)

`screenIndex` required the saver window to be exactly contained in a screen frame
(`NSContainsRect`). The modern host's window does not match screen bounds exactly, so the
lookup returned `NSNotFound` and the resume position was silently neither saved nor
restored. The lookup now falls back to the screen with the largest intersection area, then
to screen 0.

### 3.7 Insecure/deprecated archiving of resume data (`SaveHollywoodView.m`)

* Removed the `Gestalt()` runtime version check and the `_useKeyedArchiverForLeftOffData`
flag: with a 12.0 floor the keyed-archiver path is always available, so the
`NSArchiver`/`NSUnarchiver` fallbacks (deprecated, insecure) were deleted along with the
legacy `screen#` defaults key.
* Writing: `archivedDataWithRootObject:requiringSecureCoding:YES error:` — failures are
now logged.
* Reading: `unarchivedObjectOfClasses:fromData:error:` with an allow-list of
`NSDictionary`, `NSString`, `NSValue`, `NSURL`.
* Old keyed data written by 2.6 remains readable; pre-10.12 `NSArchiver` data is dropped
(the resume position is transient, losing it once is harmless).

### 3.8 Preview audio (`SaveHollywoodView.m`)

Volume is now only applied when `_preview == NO`; preview playback is always muted.

### 3.9 Main-display detection broken on multi-display setups (`SaveHollywoodView.m`)

`_mainScreen` was decided at init time from the frame origin (`NSMinX/NSMinY == 0`),
which assumed saver windows use global screen coordinates. The modern host gives every
display's view a zero origin, so every instance considered itself the main display. This
broke both "Main display only" and "Play audio only on main display": every display played
video with its own audio stream, and the independent players drifted out of sync.
`_mainScreen` is now re-evaluated in `startAnimation` (when the window exists) by
comparing `self.window.screen` against `[NSScreen screens].firstObject` (the primary
display), with a largest-intersection `screenIndex` fallback.

## 4. Known remaining deprecation warnings (functional, left as-is)

* `-[AVAsset naturalSize]` (`SaveHollywoodView.m`) — the suggested replacement
(`tracksWithMediaType:`) is itself deprecated in favor of the async load API; migrating
means restructuring the (synchronous) layout path.
* `-[AVAsset loadValuesAsynchronouslyForKeys:completionHandler:]` — same situation.
* `-[NSWorkspace typeOfFile:error:]` (three sites) — replacement is
`NSURL getResourceValue:forKey:` with `NSURLContentTypeKey`; straightforward but was out
of scope for this pass.
* Project-file cruft reported by Xcode: duplicate xib entries in the Copy Bundle Resources
phase, a legacy Rez build phase, and `CFBundleIdentifier` vs. empty
`PRODUCT_BUNDLE_IDENTIFIER`.

## 5. Verification performed

* Release build succeeds with Xcode 27.0 beta / macOS 27 SDK, arm64, zero errors.
* Folder picking, drag & drop, sheet dismissal, playback, unlock shutdown, muted preview,
and resume-from-last-position exercised manually on macOS 27.0 beta.
* Secure-coding round-trip of the resume dictionary (`NSValue`+`CMTime`, `NSURL`)
verified with a standalone test program.
24 changes: 13 additions & 11 deletions SaveHollywood.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@
F40B998A16AB1F6200644F61 /* NSColor+String.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSColor+String.m"; sourceTree = "<group>"; };
F4121CCC16FD06200059A794 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localized.strings; sourceTree = "<group>"; };
F434961A16B701B500C549DF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localized.strings; sourceTree = "<group>"; };
F44979562B53010100A1558A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; };
F44979582B53010B00A1558A /* ScreenSaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScreenSaver.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/ScreenSaver.framework; sourceTree = DEVELOPER_DIR; };
F449795A2B53011B00A1558A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
F449795C2B53017B00A1558A /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/CoreMedia.framework; sourceTree = DEVELOPER_DIR; };
F449795E2B5301B200A1558A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; };
F44979562B53010100A1558A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
F44979582B53010B00A1558A /* ScreenSaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScreenSaver.framework; path = System/Library/Frameworks/ScreenSaver.framework; sourceTree = SDKROOT; };
F449795A2B53011B00A1558A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
F449795C2B53017B00A1558A /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
F449795E2B5301B200A1558A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
F45A65162117329400E5A4D4 /* SHPlayingAssetsRegister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHPlayingAssetsRegister.h; sourceTree = "<group>"; };
F45A65172117329400E5A4D4 /* SHPlayingAssetsRegister.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SHPlayingAssetsRegister.m; sourceTree = "<group>"; };
F4875FC3211A50F1007D8759 /* SHView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHView.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -361,7 +361,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
ARCHS = "$(ARCHS_STANDARD)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
Expand All @@ -379,17 +379,18 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 12.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx10.9;
OTHER_LDFLAGS = "-framework UniformTypeIdentifiers";
SDKROOT = macosx;
};
name = Debug;
};
F40427E1169B76CB00B146F7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
ARCHS = "$(ARCHS_STANDARD)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
Expand All @@ -401,8 +402,9 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
SDKROOT = macosx10.9;
MACOSX_DEPLOYMENT_TARGET = 12.0;
OTHER_LDFLAGS = "-framework UniformTypeIdentifiers";
SDKROOT = macosx;
};
name = Release;
};
Expand Down
2 changes: 1 addition & 1 deletion SaveHollywood/NSColor+String.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ + (NSColor *)colorFromString:(NSString *)inString

- (NSString *)stringValue
{
NSColor *tColor = [self colorUsingColorSpaceName:@"NSCalibratedRGBColorSpace"];
NSColor *tColor = [self colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];

return([NSString stringWithFormat:@"%f|%f|%f",(float)[tColor redComponent],(float)[tColor greenComponent],(float)[tColor blueComponent]]);
}
Expand Down
2 changes: 1 addition & 1 deletion SaveHollywood/SHAboutBoxWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ - (void)windowDidLoad

_versionLabel.stringValue=[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Version %@ (%@)",@"Localized",tBundle,@""),tInfoDictionary[@"CFBundleShortVersionString"],tInfoDictionary[@"CFBundleVersion"]];

_copyrightLabel.stringValue=[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Legal terms",@"Localized",tBundle,@""),[[NSCalendarDate date] yearOfCommonEra]];
_copyrightLabel.stringValue=[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Legal terms",@"Localized",tBundle,@""),(long)[[NSCalendar currentCalendar] component:NSCalendarUnitYear fromDate:[NSDate date]]];
}

@end
Loading