#import #import #import "LauncherPreferences.h" #import "UIKit+hook.h" #import "utils.h" __weak UIWindow *mainWindow, *externalWindow; void swizzle(Class class, SEL originalAction, SEL swizzledAction) { method_exchangeImplementations(class_getInstanceMethod(class, originalAction), class_getInstanceMethod(class, swizzledAction)); } void swizzleClass(Class class, SEL originalAction, SEL swizzledAction) { method_exchangeImplementations(class_getClassMethod(class, originalAction), class_getClassMethod(class, swizzledAction)); } void swizzleUIImageMethod(SEL originalAction, SEL swizzledAction) { Class class = [UIImage class]; Method originalMethod = class_getInstanceMethod(class, originalAction); Method swizzledMethod = class_getInstanceMethod(class, swizzledAction); if (originalMethod && swizzledMethod) { method_exchangeImplementations(originalMethod, swizzledMethod); } else { NSLog(@"[UIKit+hook] Warning: Could not swizzle UIImage methods (%@ and %@)", NSStringFromSelector(originalAction), NSStringFromSelector(swizzledAction)); } } void init_hookUIKitConstructor(void) { swizzle(UIDevice.class, @selector(userInterfaceIdiom), @selector(hook_userInterfaceIdiom)); swizzle(UIImageView.class, @selector(setImage:), @selector(hook_setImage:)); // Add this line to swizzle the _imageWithSize: method swizzleUIImageMethod(NSSelectorFromString(@"_imageWithSize:"), @selector(hook_imageWithSize:)); if (realUIIdiom == UIUserInterfaceIdiomTV) { if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // If you are about to test iPadOS idiom on tvOS, there's no better way for this class_setSuperclass(NSClassFromString(@"UITableConstants_Pad"), NSClassFromString(@"UITableConstants_TV")); #pragma clang diagnostic pop } swizzle(UINavigationController.class, @selector(toolbar), @selector(hook_toolbar)); swizzle(UINavigationController.class, @selector(setToolbar:), @selector(hook_setToolbar:)); swizzleClass(UISwitch.class, @selector(visualElementForTraitCollection:), @selector(hook_visualElementForTraitCollection:)); } } @implementation UIDevice(hook) - (NSString *)completeOSVersion { return [NSString stringWithFormat:@"%@ %@ (%@)", self.systemName, self.systemVersion, self.buildVersion]; } - (UIUserInterfaceIdiom)hook_userInterfaceIdiom { if (getPrefBool(@"debug.debug_ipad_ui")) { return UIUserInterfaceIdiomPad; } else if (self.hook_userInterfaceIdiom == UIUserInterfaceIdiomTV) { return self.hook_userInterfaceIdiom; } else { return UIUserInterfaceIdiomPhone; } } @end // Patch: emulate scaleToFill for table views @implementation UIImageView(hook) - (BOOL)isSizeFixed { return [objc_getAssociatedObject(self, @selector(isSizeFixed)) boolValue]; } - (void)setIsSizeFixed:(BOOL)fixed { objc_setAssociatedObject(self, @selector(isSizeFixed), @(fixed), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)hook_setImage:(UIImage *)image { if (self.isSizeFixed) { UIImage *resizedImage = [image _imageWithSize:self.frame.size]; [self hook_setImage:resizedImage]; } else { [self hook_setImage:image]; } } @end // Implementation of UIImage hook for proper sizing across iOS versions @implementation UIImage(hook) - (UIImage *)hook_imageWithSize:(CGSize)size { if (CGSizeEqualToSize(self.size, size)) { return self; } UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat]; format.scale = self.scale; UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format]; UIImage *newImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull context) { // Calculate proper proportions CGFloat widthRatio = size.width / self.size.width; CGFloat heightRatio = size.height / self.size.height; CGFloat ratio = MIN(widthRatio, heightRatio); CGFloat newWidth = self.size.width * ratio; CGFloat newHeight = self.size.height * ratio; // Center the image CGFloat x = (size.width - newWidth) / 2; CGFloat y = (size.height - newHeight) / 2; [self drawInRect:CGRectMake(x, y, newWidth, newHeight)]; }]; return [newImage imageWithRenderingMode:self.renderingMode]; } @end // Patch: unimplemented get/set UIToolbar functions on tvOS @implementation UINavigationController(hook) - (UIToolbar *)hook_toolbar { UIToolbar *toolbar = objc_getAssociatedObject(self, @selector(toolbar)); if (toolbar == nil) { toolbar = [[UIToolbar alloc] initWithFrame: CGRectMake(self.view.bounds.origin.x, self.view.bounds.size.height - 100, self.view.bounds.size.width, 100)]; toolbar.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; toolbar.backgroundColor = UIColor.systemBackgroundColor; objc_setAssociatedObject(self, @selector(toolbar), toolbar, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self performSelector:@selector(_configureToolbar)]; } return toolbar; } - (void)hook_setToolbar:(UIToolbar *)toolbar { objc_setAssociatedObject(self, @selector(toolbar), toolbar, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end // Patch: UISwitch crashes if platform == tvOS @implementation UISwitch(hook) + (id)hook_visualElementForTraitCollection:(UITraitCollection *)collection { if (collection.userInterfaceIdiom == UIUserInterfaceIdiomTV) { UITraitCollection *override = [UITraitCollection traitCollectionWithUserInterfaceIdiom:UIUserInterfaceIdiomPad]; UITraitCollection *new = [UITraitCollection traitCollectionWithTraitsFromCollections:@[collection, override]]; return [self hook_visualElementForTraitCollection:new]; } return [self hook_visualElementForTraitCollection:collection]; } @end @implementation UITraitCollection(hook) - (UIUserInterfaceSizeClass)horizontalSizeClass { return UIUserInterfaceSizeClassRegular; } - (UIUserInterfaceSizeClass)verticalSizeClass { return UIUserInterfaceSizeClassRegular; } @end @implementation UIWindow(hook) + (UIWindow *)mainWindow { return mainWindow; } + (UIWindow *)externalWindow { return externalWindow; } - (UIViewController *)visibleViewController { UIViewController *current = self.rootViewController; while (current.presentedViewController) { if ([current.presentedViewController isKindOfClass:UIAlertController.class] || [current.presentedViewController isKindOfClass:NSClassFromString(@"UIInputWindowController")]) { break; } current = current.presentedViewController; } if ([current isKindOfClass:UINavigationController.class]) { return [(UINavigationController *)self.rootViewController visibleViewController]; } else { return current; } } @end // This forces the navigation bar to keep its height (44dp) in landscape @implementation UINavigationBar(forceFullHeightInLandscape) - (BOOL)forceFullHeightInLandscape { return YES; //UIScreen.mainScreen.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone; } @end UIViewController* currentVC() { return UIWindow.mainWindow.visibleViewController; }