disable carplay on ios 26db4+ and make CarPlayManager
This commit is contained in:
1 parent
d35636c0be
commit
dd902d2c30
8 files changed
+158
-123
No files matched your search
@@ -272,7 +272,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 6;
|
CURRENT_PROJECT_VERSION = 7;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<key>Pocket Poster.xcscheme_^#shared#^_</key>
|
<key>Pocket Poster.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>1</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</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 {
|
static func clearCache() throws {
|
||||||
SymHandler.cleanup()
|
SymHandler.cleanup()
|
||||||
let docDir = SymHandler.getDocumentsDirectory()
|
let docDir = SymHandler.getDocumentsDirectory()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ struct CarPlayView: View {
|
|||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
do {
|
do {
|
||||||
try pbManager.applyCarPlay(appHash: cpHash, wallpapers: wallpapers)
|
try CarPlayManager.applyCarPlay(appHash: cpHash, wallpapers: wallpapers)
|
||||||
SymHandler.cleanup() // just to be extra sure
|
SymHandler.cleanup() // just to be extra sure
|
||||||
UIApplication.shared.dismissAlert(animated: false)
|
UIApplication.shared.dismissAlert(animated: false)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
|
||||||
@@ -103,7 +103,7 @@ struct CarPlayView: View {
|
|||||||
// load active wallpapers from user defaults
|
// load active wallpapers from user defaults
|
||||||
activeWallpapers = UserDefaults.standard.array(forKey: "ActiveCarPlayWallpapers") as? [String] ?? []
|
activeWallpapers = UserDefaults.standard.array(forKey: "ActiveCarPlayWallpapers") as? [String] ?? []
|
||||||
// load the wallpapers
|
// load the wallpapers
|
||||||
let cppURL = PosterBoardManager.shared.getCarPlayPhotosURL()
|
let cppURL = CarPlayManager.getCarPlayPhotosURL()
|
||||||
let frameworkPath = "/System/Library/PrivateFrameworks/CarPlayUIServices.framework"
|
let frameworkPath = "/System/Library/PrivateFrameworks/CarPlayUIServices.framework"
|
||||||
do {
|
do {
|
||||||
for file in try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: frameworkPath), includingPropertiesForKeys: nil, options: .skipsHiddenFiles) {
|
for file in try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: frameworkPath), includingPropertiesForKeys: nil, options: .skipsHiddenFiles) {
|
||||||
@@ -127,7 +127,7 @@ struct CarPlayView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort them
|
// sort them
|
||||||
if let sortedWallpapers = PosterBoardManager.shared.getCarPlayWallpaperNames() {
|
if let sortedWallpapers = CarPlayManager.getCarPlayWallpaperNames() {
|
||||||
wallpapers = wallpapers.reorder(by: sortedWallpapers)
|
wallpapers = wallpapers.reorder(by: sortedWallpapers)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct RootView: View {
|
|||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Videos", systemImage: "camera")
|
Label("Videos", systemImage: "camera")
|
||||||
}
|
}
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if CarPlayManager.supportsCarPlay() {
|
||||||
CarPlayView()
|
CarPlayView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("CarPlay", systemImage: "car")
|
Label("CarPlay", systemImage: "car")
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct SettingsView: View {
|
|||||||
TextField("Enter PosterBoard App Hash", text: $pbHash)
|
TextField("Enter PosterBoard App Hash", text: $pbHash)
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if CarPlayManager.supportsCarPlay() {
|
||||||
TextField("Enter CarPlayWallpaper App Hash", text: $cpHash)
|
TextField("Enter CarPlayWallpaper App Hash", text: $cpHash)
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
@@ -89,7 +89,7 @@ struct SettingsView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
UserDefaults.standard.set(nil, forKey: "ActiveCarPlayWallpapers")
|
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)
|
Haptic.shared.notify(.success)
|
||||||
UIApplication.shared.alert(title: NSLocalizedString("CarPlay Applied Wallpapers Successfully Cleared!", comment: ""), body: "")
|
UIApplication.shared.alert(title: NSLocalizedString("CarPlay Applied Wallpapers Successfully Cleared!", comment: ""), body: "")
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
Reference in new issue
Block a user