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
16 changes: 16 additions & 0 deletions ControlRoom.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
C9C203ED2B17FEA80081E1EF /* LocalSearchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C203EC2B17FEA80081E1EF /* LocalSearchRowView.swift */; };
E04B47322B07B66F00DAE338 /* LocationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04B47312B07B66F00DAE338 /* LocationsController.swift */; };
E04B47342B07B72C00DAE338 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04B47332B07B72C00DAE338 /* Location.swift */; };
CE01920261019000000000010 /* PosterBoardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE01920261019000000000000 /* PosterBoardController.swift */; };
CE01920261019000000000011 /* PosterBoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE01920261019000000000001 /* PosterBoardView.swift */; };
9AABE0F726EF43318916684D /* PosterBoardArtwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = E532BC1C9BDC44BD94D254DF /* PosterBoardArtwork.swift */; };
4B86C454B2DE496D8C080990 /* PosterBoardSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0F4016BC0FC499691C339A1 /* PosterBoardSelection.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -202,6 +206,10 @@
C9C203EC2B17FEA80081E1EF /* LocalSearchRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSearchRowView.swift; sourceTree = "<group>"; };
E04B47312B07B66F00DAE338 /* LocationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsController.swift; sourceTree = "<group>"; };
E04B47332B07B72C00DAE338 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
CE01920261019000000000000 /* PosterBoardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterBoardController.swift; sourceTree = "<group>"; };
CE01920261019000000000001 /* PosterBoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterBoardView.swift; sourceTree = "<group>"; };
E532BC1C9BDC44BD94D254DF /* PosterBoardArtwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterBoardArtwork.swift; sourceTree = "<group>"; };
A0F4016BC0FC499691C339A1 /* PosterBoardSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterBoardSelection.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -354,6 +362,7 @@
isa = PBXGroup;
children = (
511BA59B23F4172400E3E660 /* SystemView.swift */,
CE01920261019000000000001 /* PosterBoardView.swift */,
51AB56E42A13D3F6002B5A67 /* DeepLinkEditorView.swift */,
);
path = SystemView;
Expand Down Expand Up @@ -440,6 +449,9 @@
5523A7E023F99D7200F25EEC /* Preferences.swift */,
ACCD798D240AE82C0004ECE5 /* PushNotification.swift */,
551F8CFD23F5C9EF0006D1BD /* SimCtl.swift */,
A0F4016BC0FC499691C339A1 /* PosterBoardSelection.swift */,
E532BC1C9BDC44BD94D254DF /* PosterBoardArtwork.swift */,
CE01920261019000000000000 /* PosterBoardController.swift */,
B07F584823F99A1700256D5D /* SimCtl+SubCommands.swift */,
551F8CFF23F5CB3A0006D1BD /* SimCtl+Types.swift */,
511BA59723F4096800E3E660 /* Simulator.swift */,
Expand Down Expand Up @@ -664,6 +676,10 @@
551F8CED23F489A50006D1BD /* SidebarView.swift in Sources */,
551F8CEF23F48B030006D1BD /* SplitLayoutView.swift in Sources */,
551F8CFE23F5C9EF0006D1BD /* SimCtl.swift in Sources */,
4B86C454B2DE496D8C080990 /* PosterBoardSelection.swift in Sources */,
9AABE0F726EF43318916684D /* PosterBoardArtwork.swift in Sources */,
CE01920261019000000000011 /* PosterBoardView.swift in Sources */,
CE01920261019000000000010 /* PosterBoardController.swift in Sources */,
E04B47322B07B66F00DAE338 /* LocationsController.swift in Sources */,
54C2092B27DBDE2200E47016 /* DocumentPickerConfig.swift in Sources */,
ACCD798E240AE82C0004ECE5 /* PushNotification.swift in Sources */,
Expand Down
85 changes: 85 additions & 0 deletions ControlRoom/Controllers/PosterBoardArtwork.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Foundation
import ImageIO

extension PosterBoardController {
// Use Collections' Still renderer. A higher contentVersion prevents WallpaperKit
// from silently substituting the built-in preset with the same identifier.
// Commit the manifest only after its referenced image has been encoded.
static func replaceArtwork(in bundleURL: URL, imageURL: URL) throws {
let manager = FileManager.default
let manifestURL = bundleURL.appendingPathComponent("Wallpaper.plist")
let originalURL = bundleURL.appendingPathComponent(".ControlRoomOriginalWallpaper.plist")
let manifest = try readManifest(manifestURL)
guard bundleURL.pathExtension == "wallpaper", manifest["assets"] is [String: Any] else {
throw OperationError(message: "This provider does not contain replaceable wallpaper artwork.")
}
guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, nil),
let image = CGImageSourceCreateThumbnailAtIndex(source, 0, [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: 4096
] as CFDictionary) else {
throw OperationError(message: "The selected file could not be read as an image.")
}
let filename = "ControlRoomImage-\(UUID().uuidString).jpg"
let output = bundleURL.appendingPathComponent(filename)
do {
let encoded = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(encoded, "public.jpeg" as CFString, 1, nil) else {
throw OperationError(message: "The wallpaper image could not be prepared.")
}
CGImageDestinationAddImage(destination, image, [kCGImageDestinationLossyCompressionQuality: 0.95] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { throw OperationError(message: "The wallpaper image could not be encoded.") }
try (encoded as Data).write(to: output, options: .atomic)
var updated = manifest
let asset: [String: Any] = ["type": "Still", "fullSizeImageFileName": filename, "thumbnailImageFileName": filename,
"identifier": manifest["identifier"] ?? 0, "name": "Image"]
updated["assets"] = ["lockAndHome": ["default": asset, "dark": asset, "inactive": asset]]
updated["appearanceAware"] = false
updated["logicalScreenClass"] = "any"
updated["disableAdaptiveTime"] = false
updated["preferredProminentColor"] = ["default": "#FFFFFF", "dark": "#FFFFFF"]
updated["contentVersion"] = max((manifest["contentVersion"] as? NSNumber)?.doubleValue ?? 1, 1) + 1
if !manager.fileExists(atPath: originalURL.path) {
try Data(contentsOf: manifestURL).write(to: originalURL, options: .atomic)
}
try PropertyListSerialization.data(fromPropertyList: updated, format: .xml, options: 0).write(to: manifestURL, options: .atomic)
} catch {
try? manager.removeItem(at: output)
throw error
}
try removeGeneratedArtwork(in: bundleURL, except: filename)
}

static func restoreArtwork(in bundleURL: URL) throws {
let manager = FileManager.default
let original = bundleURL.appendingPathComponent(".ControlRoomOriginalWallpaper.plist")
let data = try Data(contentsOf: original)
_ = try readManifest(original)
try data.write(to: bundleURL.appendingPathComponent("Wallpaper.plist"), options: .atomic)
try removeGeneratedArtwork(in: bundleURL, except: nil)
try manager.removeItem(at: original)
}

private static func removeGeneratedArtwork(in bundleURL: URL, except retained: String?) throws {
let manager = FileManager.default
for url in try manager.contentsOfDirectory(at: bundleURL, includingPropertiesForKeys: nil) {
let name = url.deletingPathExtension().lastPathComponent
guard url.pathExtension == "jpg", name.hasPrefix("ControlRoomImage-"),
UUID(uuidString: String(name.dropFirst("ControlRoomImage-".count))) != nil,
url.lastPathComponent != retained else { continue }
try requireContained(url, in: bundleURL)
try manager.removeItem(at: url)
}
}

static func clearSnapshots(in versionURL: URL) throws {
let manager = FileManager.default
for url in try manager.contentsOfDirectory(at: versionURL, includingPropertiesForKeys: nil) {
guard url.lastPathComponent.hasPrefix("RuntimeSnapshot") || ["scratch", "FallbackSnapshots"].contains(url.lastPathComponent) else { continue }
try requireContained(url, in: versionURL)
try manager.removeItem(at: url)
}
}

}
Loading