From dd902d2c3008782acf3c337400026ed79b12966d Mon Sep 17 00:00:00 2001 From: leminlimez Date: Tue, 12 Aug 2025 14:45:49 -0400 Subject: [PATCH] disable carplay on ios 26db4+ and make CarPlayManager --- Pocket Poster.xcodeproj/project.pbxproj | 4 +- .../xcschemes/xcschememanagement.plist | 2 +- .../CarPlayWallpaper+CarPlayManager.swift | 149 ++++++++++++++++++ .../Controllers/CarPlayWallpaper.swift | 22 --- .../Controllers/PosterBoardManager.swift | 92 ----------- .../Views/NavigatorViews/CarPlayView.swift | 6 +- Pocket Poster/Views/RootView.swift | 2 +- Pocket Poster/Views/SettingsView.swift | 4 +- 8 files changed, 158 insertions(+), 123 deletions(-) create mode 100644 Pocket Poster/Controllers/CarPlayWallpaper+CarPlayManager.swift delete mode 100644 Pocket Poster/Controllers/CarPlayWallpaper.swift diff --git a/Pocket Poster.xcodeproj/project.pbxproj b/Pocket Poster.xcodeproj/project.pbxproj index 634d3bf..b189ef4 100644 --- a/Pocket Poster.xcodeproj/project.pbxproj +++ b/Pocket Poster.xcodeproj/project.pbxproj @@ -272,7 +272,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\""; DEVELOPMENT_TEAM = 2P4CP47H76; ENABLE_PREVIEWS = YES; @@ -304,7 +304,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\""; DEVELOPMENT_TEAM = 2P4CP47H76; ENABLE_PREVIEWS = YES; diff --git a/Pocket Poster.xcodeproj/xcuserdata/lemin.xcuserdatad/xcschemes/xcschememanagement.plist b/Pocket Poster.xcodeproj/xcuserdata/lemin.xcuserdatad/xcschemes/xcschememanagement.plist index d7a896f..f3372ed 100644 --- a/Pocket Poster.xcodeproj/xcuserdata/lemin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Pocket Poster.xcodeproj/xcuserdata/lemin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Pocket Poster.xcscheme_^#shared#^_ orderHint - 1 + 0 diff --git a/Pocket Poster/Controllers/CarPlayWallpaper+CarPlayManager.swift b/Pocket Poster/Controllers/CarPlayWallpaper+CarPlayManager.swift new file mode 100644 index 0000000..6a094f5 --- /dev/null +++ b/Pocket Poster/Controllers/CarPlayWallpaper+CarPlayManager.swift @@ -0,0 +1,149 @@ +// +// CarPlayWallpaper.swift +// Pocket Poster +// +// Created by lemin on 6/22/25. +// + +import SwiftUI + +struct CarPlayWallpaper: Identifiable { + var id = UUID() + var name: String + var lightImage: UIImage + var darkImage: UIImage + var selectedImageDataLight: Data? + var selectedImageDataDark: Data? +} + +extension CarPlayWallpaper: Reorderable { + typealias OrderElement = String + var orderElement: OrderElement { name } +} + +class CarPlayManager { + static func supportsCarPlay() -> Bool { + if UIDevice.current.userInterfaceIdiom != .phone { + // CarPlay is not on iPads + return false + } + if #available(iOS 19, *) { + // get the build number + var osVersionString = [CChar](repeating: 0, count: 16) + var osVersionStringLen = size_t(osVersionString.count - 1) + + let result = sysctlbyname("kern.osversion", &osVersionString, &osVersionStringLen, nil, 0) + + if result == 0 { + // Convert C array to String + if let build = String(validatingUTF8: osVersionString) { + // check build number for iOS 26 dev beta 1-3, return true if user is on that + if build == "23A5260n" // dev beta 1 + || build == "23A5260u" // dev beta 1 (iPhone 15/16 series) + || build == "23A5276f" // dev beta 2 + || build == "23A5287g" { // dev beta 3 + return true + } + } else { + print("Failed to convert build number to String") + } + } else { + print("sysctlbyname failed with error: \(String(cString: strerror(errno)))") + } + return false + } + return true + } + + static func getCarPlayCacheVersion() -> String { + // they started adding numbers to the end in iOS 18, this is to compensate for that + if #available(iOS 19.0, *) { + return "-12" + } else if #available(iOS 18.0, *) { + return "-11" + } + return "" + } + + static func getCarPlayWallpaperNames() -> [String]? { + dlopen("/System/Library/PrivateFrameworks/CarPlayUIServices.framework/CarPlayUIServices", RTLD_GLOBAL) + + if #available(iOS 18.0, *) { + // iOS 18 method (it got moved) + guard let obj = objc_getClass("CRSUISystemWallpaper") as? NSObject else { print("no class"); return nil } + + if let success = obj.perform(Selector(("wallpapers"))), let arr = success.takeUnretainedValue() as? [NSObject] { + var namesList: [String] = [] + for wp in arr { + if let wpACName = wp.perform(Selector(("wallpaperAssetCatalogName"))), let result = wpACName.takeUnretainedValue() as? String { + namesList.append(result) + } + } + return namesList + } + } else { + // iOS 17- + guard let obj = objc_getClass("CRSUIWallpaperPreferences") as? NSObject else { print("no class"); return nil } + if let success = obj.perform(Selector(("availableWallpapers"))), let arr = success.takeUnretainedValue() as? [NSObject] { + var namesList: [String] = [] + for wp in arr { + if let wpACName = wp.perform(Selector(("wallpaperAssetCatalogName"))), let result = wpACName.takeUnretainedValue() as? String { + namesList.append(result) + } + } + return namesList + } + } + + return nil + } + + static func getCarPlayPhotosURL() -> URL { + let cppURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CarPlayPhotos", conformingTo: .directory) + // create it if it doesn't exist + if !FileManager.default.fileExists(atPath: cppURL.path()) { + try? FileManager.default.createDirectory(at: cppURL, withIntermediateDirectories: true) + } + return cppURL + } + + static func applyCarPlay(appHash: String, wallpapers: [CarPlayWallpaper]) throws { + // write the image + var toRemove: [URL] = [] + var activeWP: [String] = UserDefaults.standard.array(forKey: "ActiveCarPlayWallpapers") as? [String] ?? [] + let cppURL = getCarPlayPhotosURL() + let cacheVer = getCarPlayCacheVersion() + for wallpaper in wallpapers { + if let data = wallpaper.selectedImageDataLight, let img = UIImage(data: data) { + let imgURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CAR\(wallpaper.name)Dynamic-Light\(cacheVer).cpbitmap") + img.writeToCPBitmapFile(to: imgURL.path() as NSString) + try? data.write(to: cppURL.appendingPathComponent("\(wallpaper.name)-Light")) + toRemove.append(imgURL) + if !activeWP.contains(wallpaper.name) { + activeWP.append(wallpaper.name) + } + } + if let data = wallpaper.selectedImageDataDark, let img = UIImage(data: data) { + let imgURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CAR\(wallpaper.name)Dynamic-Dark\(cacheVer).cpbitmap") + img.writeToCPBitmapFile(to: imgURL.path() as NSString) + try? data.write(to: cppURL.appendingPathComponent("\(wallpaper.name)-Dark")) + toRemove.append(imgURL) + if !activeWP.contains(wallpaper.name) { + activeWP.append(wallpaper.name) + } + } + } + + // symlink and apply + let _ = try SymHandler.createAppSymlink(for: "\(appHash)/Library/Caches/MappedImageCache/com.apple.CarPlayApp.wallpaper-images") + defer { + SymHandler.cleanup() + } + for imgURL in toRemove { + try FileManager.default.trashItem(at: imgURL, resultingItemURL: nil) + } + UserDefaults.standard.set(activeWP, forKey: "ActiveCarPlayWallpapers") + let test = SymHandler.getDocumentsDirectory().appendingPathComponent("Caches") + try Data(count: 0).write(to: test) + } +} diff --git a/Pocket Poster/Controllers/CarPlayWallpaper.swift b/Pocket Poster/Controllers/CarPlayWallpaper.swift deleted file mode 100644 index c636249..0000000 --- a/Pocket Poster/Controllers/CarPlayWallpaper.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// CarPlayWallpaper.swift -// Pocket Poster -// -// Created by lemin on 6/22/25. -// - -import SwiftUI - -struct CarPlayWallpaper: Identifiable { - var id = UUID() - var name: String - var lightImage: UIImage - var darkImage: UIImage - var selectedImageDataLight: Data? - var selectedImageDataDark: Data? -} - -extension CarPlayWallpaper: Reorderable { - typealias OrderElement = String - var orderElement: OrderElement { name } -} diff --git a/Pocket Poster/Controllers/PosterBoardManager.swift b/Pocket Poster/Controllers/PosterBoardManager.swift index 7b925c1..5ee7025 100644 --- a/Pocket Poster/Controllers/PosterBoardManager.swift +++ b/Pocket Poster/Controllers/PosterBoardManager.swift @@ -235,98 +235,6 @@ class PosterBoardManager: ObservableObject { } } - func getCarPlayCacheVersion() -> String { - // they started adding numbers to the end in iOS 18, this is to compensate for that - if #available(iOS 19.0, *) { - return "-12" - } else if #available(iOS 18.0, *) { - return "-11" - } - return "" - } - - func getCarPlayWallpaperNames() -> [String]? { - dlopen("/System/Library/PrivateFrameworks/CarPlayUIServices.framework/CarPlayUIServices", RTLD_GLOBAL) - - if #available(iOS 18.0, *) { - // iOS 18 method (it got moved) - guard let obj = objc_getClass("CRSUISystemWallpaper") as? NSObject else { print("no class"); return nil } - - if let success = obj.perform(Selector(("wallpapers"))), let arr = success.takeUnretainedValue() as? [NSObject] { - var namesList: [String] = [] - for wp in arr { - if let wpACName = wp.perform(Selector(("wallpaperAssetCatalogName"))), let result = wpACName.takeUnretainedValue() as? String { - namesList.append(result) - } - } - return namesList - } - } else { - // iOS 17- - guard let obj = objc_getClass("CRSUIWallpaperPreferences") as? NSObject else { print("no class"); return nil } - if let success = obj.perform(Selector(("availableWallpapers"))), let arr = success.takeUnretainedValue() as? [NSObject] { - var namesList: [String] = [] - for wp in arr { - if let wpACName = wp.perform(Selector(("wallpaperAssetCatalogName"))), let result = wpACName.takeUnretainedValue() as? String { - namesList.append(result) - } - } - return namesList - } - } - - return nil - } - - func getCarPlayPhotosURL() -> URL { - let cppURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CarPlayPhotos", conformingTo: .directory) - // create it if it doesn't exist - if !FileManager.default.fileExists(atPath: cppURL.path()) { - try? FileManager.default.createDirectory(at: cppURL, withIntermediateDirectories: true) - } - return cppURL - } - - func applyCarPlay(appHash: String, wallpapers: [CarPlayWallpaper]) throws { - // write the image - var toRemove: [URL] = [] - var activeWP: [String] = UserDefaults.standard.array(forKey: "ActiveCarPlayWallpapers") as? [String] ?? [] - let cppURL = getCarPlayPhotosURL() - let cacheVer = getCarPlayCacheVersion() - for wallpaper in wallpapers { - if let data = wallpaper.selectedImageDataLight, let img = UIImage(data: data) { - let imgURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CAR\(wallpaper.name)Dynamic-Light\(cacheVer).cpbitmap") - img.writeToCPBitmapFile(to: imgURL.path() as NSString) - try? data.write(to: cppURL.appendingPathComponent("\(wallpaper.name)-Light")) - toRemove.append(imgURL) - if !activeWP.contains(wallpaper.name) { - activeWP.append(wallpaper.name) - } - } - if let data = wallpaper.selectedImageDataDark, let img = UIImage(data: data) { - let imgURL = SymHandler.getDocumentsDirectory().appendingPathComponent("CAR\(wallpaper.name)Dynamic-Dark\(cacheVer).cpbitmap") - img.writeToCPBitmapFile(to: imgURL.path() as NSString) - try? data.write(to: cppURL.appendingPathComponent("\(wallpaper.name)-Dark")) - toRemove.append(imgURL) - if !activeWP.contains(wallpaper.name) { - activeWP.append(wallpaper.name) - } - } - } - - // symlink and apply - let _ = try SymHandler.createAppSymlink(for: "\(appHash)/Library/Caches/MappedImageCache/com.apple.CarPlayApp.wallpaper-images") - defer { - SymHandler.cleanup() - } - for imgURL in toRemove { - try FileManager.default.trashItem(at: imgURL, resultingItemURL: nil) - } - UserDefaults.standard.set(activeWP, forKey: "ActiveCarPlayWallpapers") - let test = SymHandler.getDocumentsDirectory().appendingPathComponent("Caches") - try Data(count: 0).write(to: test) - } - static func clearCache() throws { SymHandler.cleanup() let docDir = SymHandler.getDocumentsDirectory() diff --git a/Pocket Poster/Views/NavigatorViews/CarPlayView.swift b/Pocket Poster/Views/NavigatorViews/CarPlayView.swift index 952a97f..81e4a58 100644 --- a/Pocket Poster/Views/NavigatorViews/CarPlayView.swift +++ b/Pocket Poster/Views/NavigatorViews/CarPlayView.swift @@ -52,7 +52,7 @@ struct CarPlayView: View { DispatchQueue.global(qos: .userInitiated).async { do { - try pbManager.applyCarPlay(appHash: cpHash, wallpapers: wallpapers) + try CarPlayManager.applyCarPlay(appHash: cpHash, wallpapers: wallpapers) SymHandler.cleanup() // just to be extra sure UIApplication.shared.dismissAlert(animated: false) DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: { @@ -103,7 +103,7 @@ struct CarPlayView: View { // load active wallpapers from user defaults activeWallpapers = UserDefaults.standard.array(forKey: "ActiveCarPlayWallpapers") as? [String] ?? [] // load the wallpapers - let cppURL = PosterBoardManager.shared.getCarPlayPhotosURL() + let cppURL = CarPlayManager.getCarPlayPhotosURL() let frameworkPath = "/System/Library/PrivateFrameworks/CarPlayUIServices.framework" do { for file in try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: frameworkPath), includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { @@ -127,7 +127,7 @@ struct CarPlayView: View { } } // sort them - if let sortedWallpapers = PosterBoardManager.shared.getCarPlayWallpaperNames() { + if let sortedWallpapers = CarPlayManager.getCarPlayWallpaperNames() { wallpapers = wallpapers.reorder(by: sortedWallpapers) } } catch { diff --git a/Pocket Poster/Views/RootView.swift b/Pocket Poster/Views/RootView.swift index ff3a114..7a85a46 100644 --- a/Pocket Poster/Views/RootView.swift +++ b/Pocket Poster/Views/RootView.swift @@ -18,7 +18,7 @@ struct RootView: View { .tabItem { Label("Videos", systemImage: "camera") } - if UIDevice.current.userInterfaceIdiom == .phone { + if CarPlayManager.supportsCarPlay() { CarPlayView() .tabItem { Label("CarPlay", systemImage: "car") diff --git a/Pocket Poster/Views/SettingsView.swift b/Pocket Poster/Views/SettingsView.swift index a7e39e8..f10275d 100644 --- a/Pocket Poster/Views/SettingsView.swift +++ b/Pocket Poster/Views/SettingsView.swift @@ -23,7 +23,7 @@ struct SettingsView: View { TextField("Enter PosterBoard App Hash", text: $pbHash) .textFieldStyle(RoundedBorderTextFieldStyle()) .font(.system(.body, design: .monospaced)) - if UIDevice.current.userInterfaceIdiom == .phone { + if CarPlayManager.supportsCarPlay() { TextField("Enter CarPlayWallpaper App Hash", text: $cpHash) .textFieldStyle(RoundedBorderTextFieldStyle()) .font(.system(.body, design: .monospaced)) @@ -89,7 +89,7 @@ struct SettingsView: View { Button(action: { UIImpactFeedbackGenerator(style: .soft).impactOccurred() UserDefaults.standard.set(nil, forKey: "ActiveCarPlayWallpapers") - try? FileManager.default.removeItem(at: PosterBoardManager.shared.getCarPlayPhotosURL()) + try? FileManager.default.removeItem(at: CarPlayManager.getCarPlayPhotosURL()) Haptic.shared.notify(.success) UIApplication.shared.alert(title: NSLocalizedString("CarPlay Applied Wallpapers Successfully Cleared!", comment: ""), body: "") }) {