disable carplay on ios 26db4+ and make CarPlayManager

This commit is contained in:
leminlimez committed 2025-08-12 14:45:49 -04:00
1 parent d35636c0be
commit dd902d2c30
8 files changed
+158 -123

No files matched your search

+2 -2
View File
@@ -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;
@@ -7,7 +7,7 @@
<key>Pocket Poster.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
@@ -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)
}
}
@@ -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 }
}
@@ -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()
@@ -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 {
+1 -1
View File
@@ -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")
+2 -2
View File
@@ -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: "")
}) {