Repository: khanhduytran0/TrollPad Branch: main Commit: 3a841b3aa26d Files: 25 Total size: 32.4 KB Directory structure: gitextract_gbrkdi9r/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── Makefile ├── README.md ├── SpringBoard.h ├── TPPrefsObserver.h ├── TPPrefsObserver.m ├── TrollPadPrefs/ │ ├── DBSMultitaskingContinuousExposeController+hook.x │ ├── Makefile │ ├── NSUserDefaults+hook.x │ ├── Resources/ │ │ ├── Info.plist │ │ └── Root.plist │ ├── TPPRootListController.h │ ├── TPPRootListController.m │ ├── layout/ │ │ └── Library/ │ │ └── PreferenceLoader/ │ │ └── Preferences/ │ │ └── TrollPad.plist │ └── libfeatureflags+hook.x ├── TrollPadSB.plist ├── TrollPadUI.plist ├── TweakSB.x ├── TweakUI.x ├── UIKitPrivate.h ├── control └── layout/ ├── DEBIAN/ │ ├── postinst │ └── postrm └── Library/ └── ControlCenter/ └── Bundles/ └── ContinuousExposeModule.bundle/ └── .keep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Theos CI on: push: pull_request: jobs: build: runs-on: macos-14 steps: - uses: actions/checkout@main - name: Checkout theos/theos uses: actions/checkout@main with: repository: theos/theos ref: master submodules: recursive path: theos - name: Checkout theos/sdks uses: actions/checkout@main with: repository: theos/sdks ref: master sparse-checkout: iPhoneOS16.5.sdk path: theos/sdks - name: Ensure main utils are installed uses: dhinakg/procursus-action@main with: packages: coreutils make xz ldid - name: Build run: | export THEOS=theos gmake package FINALPACKAGE=1 gmake package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless #gmake package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=roothide - name: Upload packages uses: actions/upload-artifact@main with: name: packages path: packages/*.deb ================================================ FILE: .gitignore ================================================ .theos/ packages/ .DS_Store ================================================ FILE: Makefile ================================================ ARCHS := arm64 arm64e TARGET := iphone:clang:16.5:15.0 INSTALL_TARGET_PROCESSES = SpringBoard include $(THEOS)/makefiles/common.mk # TARGET_CODESIGN = fastPathSign TWEAK_NAME = TrollPadSB TrollPadUI TrollPadSB_FILES = TweakSB.x TrollPadPrefs/NSUserDefaults+hook.x TPPrefsObserver.m TrollPadSB_CFLAGS = -fobjc-arc TrollPadSB_LIBRARIES = MobileGestalt # TrollPad_PRIVATE_FRAMEWORKS = BoardServices SpringBoard TrollPadUI_FILES = TweakUI.x TrollPadUI_CFLAGS = -fobjc-arc TrollPadUI_LIBRARIES = root include $(THEOS_MAKE_PATH)/tweak.mk SUBPROJECTS += TrollPadPrefs include $(THEOS_MAKE_PATH)/aggregate.mk ================================================ FILE: README.md ================================================ # TrollPad Troll SpringBoard into thinking it's running on iPadOS ## Features - Grid app switcher - Multitasking button - Floating dock + Recent apps + App Library - Split View - Slide Over - Stage Manager + Requires iOS 16 or later + External display supports AirPlay too! - Floating keyboard + Only works on ios 16 or later for now ### Enabling Stage Manager > [!WARNING] > It is recommended to enable Stage Manager using Control Center module only, so that there will be no risk of bootloop. - Add Stage Manager toggle to Control Center For external display, the behavior is same as on iPadOS: plug and play. ## Side effects - In multitasking modes in landscape, left and right are affected by safe area layout margins. This issue doesn't happen in external display. ================================================ FILE: SpringBoard.h ================================================ #import @interface SBApplicationInfo : NSObject @property(assign, nonatomic) UIInterfaceOrientationMask supportedInterfaceOrientations; @end @interface SBApplication : NSObject @property(nonatomic, readonly) SBApplicationInfo *info; @end @interface SBAppSwitcherSettings : NSObject @property(assign) CGFloat spacingBetweenLeadingEdgeAndIcon, spacingBetweenTrailingEdgeAndLabels; @end @interface SBExternalDisplayRuntimeAvailabilitySettings : NSObject @property(nonatomic, assign) BOOL requirePointer, requireHardwareKeyboard; @end @interface SBFluidSwitcherItemContainer : UIView - (BOOL)isResizingAllowed; @end @interface SBAppResizeGrabberView : UIView @end @interface SBWindowScene : UIWindowScene - (BOOL)isExternalDisplayWindowScene; @end ================================================ FILE: TPPrefsObserver.h ================================================ #import @interface TPPrefsObserver : NSObject @property(nonatomic, assign) BOOL allowLandscapeHomeScreen, forceEnableMedusaForLandscapeOnlyApps, hideStageManagerResizeCorners, useiPadAppSwitchingAnimation, isFloatingDockSupported, scaleGridSwitcher; @end ================================================ FILE: TPPrefsObserver.m ================================================ #import "TPPrefsObserver.h" @implementation TPPrefsObserver - (instancetype)init { self = [super init]; [self observeKey:@"TPAllowLandscapeHomeScreen"]; [self observeKey:@"TPForceEnableMedusaForLandscapeOnlyApps"]; [self observeKey:@"TPHideStageManagerResizeCorners"]; [self observeKey:@"TPUseiPadAppSwitchingAnimation"]; [self observeKey:@"TPIsFloatingDockSupported"]; [self observeKey:@"TPScaleGridSwitcher"]; // Fetch keys [self observeValueForKeyPath:nil ofObject:nil change:nil context:nil]; return self; } - (void)observeKey:(NSString *)key { [NSUserDefaults.standardUserDefaults addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:NULL]; } - (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; self.allowLandscapeHomeScreen = [defaults boolForKey:@"TPAllowLandscapeHomeScreen"]; self.forceEnableMedusaForLandscapeOnlyApps = [defaults boolForKey:@"TPForceEnableMedusaForLandscapeOnlyApps"]; self.hideStageManagerResizeCorners = [defaults boolForKey:@"TPHideStageManagerResizeCorners"]; self.useiPadAppSwitchingAnimation = [defaults boolForKey:@"TPUseiPadAppSwitchingAnimation"]; self.isFloatingDockSupported = [defaults boolForKey:@"TPIsFloatingDockSupported"]; self.scaleGridSwitcher = [defaults boolForKey:@"TPScaleGridSwitcher"]; } @end ================================================ FILE: TrollPadPrefs/DBSMultitaskingContinuousExposeController+hook.x ================================================ #import #import @interface DBSMultitaskingContinuousExposeController : PSListController @end %hook DBSMultitaskingContinuousExposeController - (NSArray *)specifiers { NSArray *specifiers = %orig; for (PSSpecifier *specifier in specifiers) { NSString *key = specifier.properties[@"key"]; if ([key hasPrefix:@"SBChamois"]) { specifier.properties[@"key"] = [@"TP" stringByAppendingString:key]; } } return specifiers; } %end %hook DBSContinuousExposeLayoutTableViewCell - (UIView *)dockOptionView { UIView *result = %orig; result.alpha = 0.5f; result.userInteractionEnabled = NO; return result; } %end ================================================ FILE: TrollPadPrefs/Makefile ================================================ include $(THEOS)/makefiles/common.mk BUNDLE_NAME = TrollPad $(BUNDLE_NAME)_FILES = DBSMultitaskingContinuousExposeController+hook.x NSUserDefaults+hook.x TPPRootListController.m $(BUNDLE_NAME)_FRAMEWORKS = UIKit CydiaSubstrate $(BUNDLE_NAME)_PRIVATE_FRAMEWORKS = Preferences $(BUNDLE_NAME)_INSTALL_PATH = /Library/PreferenceBundles $(BUNDLE_NAME)_CFLAGS = -fobjc-arc include $(THEOS_MAKE_PATH)/bundle.mk ================================================ FILE: TrollPadPrefs/NSUserDefaults+hook.x ================================================ #import // Hook this to avoid real keys from being set %hook NSUserDefaults - (void)setBool:(BOOL)value forKey:(NSString *)key { if ([key isEqualToString:@"SBChamoisHideDock"]) { // Never ever set this to YES, as it is known to respring loop %orig(NO, key); } else if ([key hasPrefix:@"SBChamois"]) { %orig(value, [NSString stringWithFormat:@"TP%@", key]); } else { %orig; } } - (BOOL)boolForKey:(NSString *)key { if ([key hasPrefix:@"SBChamois"]) { return %orig([NSString stringWithFormat:@"TP%@", key]); } else { return %orig; } } - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)key options:(NSKeyValueObservingOptions)options context:(void *)context { if ([key hasPrefix:@"SBChamois"]) { %orig(observer, [NSString stringWithFormat:@"TP%@", key], options, context); } else { %orig; } } - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)key context:(void *)context { if ([key hasPrefix:@"SBChamois"]) { %orig(observer, [NSString stringWithFormat:@"TP%@", key], context); } else { %orig; } } - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)key { if ([key hasPrefix:@"SBChamois"]) { %orig(observer, [NSString stringWithFormat:@"TP%@", key]); } else { %orig; } } %end ================================================ FILE: TrollPadPrefs/Resources/Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable TrollPad CFBundleIdentifier com.kdt.trollpad CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion 1.0 NSPrincipalClass TPPRootListController ================================================ FILE: TrollPadPrefs/Resources/Root.plist ================================================ items cell PSGroupCell label Stage Manager cell PSLinkListCell detail DBSMultitaskingContinuousExposeController id CONTINUOUS-EXPOSE label Stage Manager action openDisplayArrangement cell PSButtonCell id DISPLAY_ARRANGEMENT label Display Arrangement (doesn't work?) cell PSSwitchCell default defaults com.apple.springboard key SBExtendedDisplayOverrideSupportForAirPlayAndDontFileRadars label Use AirPlay as External Display cell PSSwitchCell default defaults com.apple.springboard key TPHideStageManagerResizeCorners label Hide resize corners cell PSGroupCell label External Display content scale cell PSSliderCell default 1 defaults com.apple.springboard key SBExtendedDisplayContentsScaleAndDontFileRadars max 3 min 1 showValue cell PSGroupCell label Miscellaneous cell PSSwitchCell default defaults com.kdt.trollpad key TPShowShortcutButtonsOnKeyboard label Show shortcut buttons on keyboard cell PSSwitchCell default defaults com.apple.springboard key TPForceEnableMedusaForLandscapeOnlyApps label Force resizable window for games cell PSSwitchCell default defaults com.apple.springboard key TPUseiPadAppSwitchingAnimation label iPadOS app switching animation cell PSSwitchCell default defaults com.apple.springboard key TPAllowLandscapeHomeScreen label Allow landscape Home Screen cell PSSwitchCell default defaults com.apple.springboard key TPIsFloatingDockSupported label Floating Dock cell PSSwitchCell default defaults com.apple.springboard id SHOW_APP_LIBRARY key SBAppLibraryInDockEnabled label Show App Library in Dock cell PSSwitchCell default defaults com.apple.springboard id ALLOW_RECENTS key SBRecentsEnabled label Show Recents in Dock cell PSSwitchCell default defaults com.apple.springboard key TPScaleGridSwitcher label Scale Grid Switcher cell PSGroupCell label About me action openSourceCode cell PSButtonCell icon GitHub.png label khanhduytran0/TrollPad action openTwitter cell PSButtonCell icon Twitter.png label @TranKha50277352 title TrollPad ================================================ FILE: TrollPadPrefs/TPPRootListController.h ================================================ #import @interface TPPRootListController : PSListController @end // Respring @interface SBSRelaunchAction : NSObject + (SBSRelaunchAction *)actionWithReason:(NSString *)reason options:(NSUInteger)options targetURL:(NSURL *)url; @end @interface FBSSystemService : NSObject + (FBSSystemService *)sharedService; - (void)sendActions:(NSSet *)actions withResult:(id *)result; @end ================================================ FILE: TrollPadPrefs/TPPRootListController.m ================================================ #import #import #import "TPPRootListController.h" #define PREF_PATH @"/var/mobile/Library/Preferences/com.kdt.trollpad.plist" @implementation TPPRootListController - (NSArray *)specifiers { if (!_specifiers) { _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Respring" style:UIBarButtonItemStylePlain target:self action:@selector(respring)]; } return _specifiers; } - (void)openDisplayArrangement { UIViewController *controller = [NSClassFromString(@"DBSArrangementViewController") new]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller]; [self presentViewController:navigationController animated:YES completion:nil]; } - (void)openSourceCode { [UIApplication.sharedApplication openURL:[NSURL URLWithString:@"https://github.com/khanhduytran0/TrollPad"] options:@{} completionHandler:nil]; } - (void)openTwitter { [UIApplication.sharedApplication openURL:[NSURL URLWithString:@"https://twitter.com/TranKha50277352"] options:@{} completionHandler:nil]; } - (void)respring { NSURL *returnURL = [NSURL URLWithString:@"prefs:root=TrollPad"]; SBSRelaunchAction *action = [NSClassFromString(@"SBSRelaunchAction") actionWithReason:@"RestartRenderServer" options:0 targetURL:returnURL]; [[NSClassFromString(@"FBSSystemService") sharedService] sendActions:[NSSet setWithObject:action] withResult:nil]; } @end ================================================ FILE: TrollPadPrefs/layout/Library/PreferenceLoader/Preferences/TrollPad.plist ================================================ entry bundle TrollPad cell PSLinkCell detail TPPRootListController icon Icon.png isController label TrollPad ================================================ FILE: TrollPadPrefs/libfeatureflags+hook.x ================================================ #import bool _os_feature_enabled_impl(const char *domain, const char *feature); %hookf(bool, _os_feature_enabled_impl, const char *domain, const char *feature) { if (!strcmp(domain, "Ensemble") && !strcmp(feature, "Enabled")) { return true; } return %orig; } ================================================ FILE: TrollPadSB.plist ================================================ { Filter = { Bundles = ( "com.apple.springboard" ); }; } ================================================ FILE: TrollPadUI.plist ================================================ { Filter = { Bundles = ( "com.apple.UIKit" ); }; } ================================================ FILE: TweakSB.x ================================================ #import #import #import "SpringBoard.h" #import "TPPrefsObserver.h" #import "UIKitPrivate.h" #include #include #include #include #include // #define DEBUG_LOG_IDIOM #ifdef DEBUG_LOG_IDIOM @interface NSThread(private) + (NSString *)ams_symbolicatedCallStackSymbols; @end #endif static TPPrefsObserver* pref; // Since some methods explicitly check for user interface idiom, I have no better way to fool them // so I just hook them, set iPad idiom when necessary and set back to iPhone after calling original static uint16_t forcePadIdiom = 0; %hook UIDevice - (UIUserInterfaceIdiom)userInterfaceIdiom { // Ever wondered how I obtained those random functions to hook? This is my way #ifdef DEBUG_LOG_IDIOM { static NSFileHandle *fileHandle; static int left = 100; static NSString *tmpOut = @"/var/mobile/Documents/stack.txt"; static NSString *realOut = @"/var/mobile/Documents/stack_out.txt"; if (!fileHandle) { fileHandle = [NSFileHandle fileHandleForWritingAtPath:tmpOut]; if (!fileHandle) { if (forcePadIdiom > 0) { return UIUserInterfaceIdiomPad; } else { return %orig; } } [fileHandle seekToEndOfFile]; } // Log every single call stack to file NSString *stackTrace = [NSThread ams_symbolicatedCallStackSymbols]; [fileHandle writeData:[stackTrace dataUsingEncoding:NSUTF8StringEncoding]]; if (left-- == 0) { left = 100; [fileHandle closeFile]; fileHandle = nil; [NSFileManager.defaultManager removeItemAtPath:realOut error:nil]; [NSFileManager.defaultManager moveItemAtPath:tmpOut toPath:realOut error:nil]; } return UIUserInterfaceIdiomPad; } #endif if (forcePadIdiom > 0) { return UIUserInterfaceIdiomPad; } else { return %orig; } } %end /* %hook SBFloatingDockView - (CGFloat)contentHeightForBounds:(CGRect)frame { if (frame.size.width > frame.size.height) { CGFloat width = frame.size.height; frame.size.height = frame.size.width; frame.size.width = width; } return %orig; } %end */ // Fix status bar for the external display %hook _UIStatusBar - (void)_prepareVisualProviderIfNeeded { UIScreen *screen = self.targetScreen ?: self._effectiveTargetScreen; if (screen._isExternal) { // For performance reason, we're gonna overwrite userInterfaceIdiom directly // userInterfaceIdiom: ldr x0, [x0, #0x8] uint64_t *collection = (uint64_t *)(__bridge void *)screen.traitCollection; if (collection) { collection[1] = UIUserInterfaceIdiomPad; } } %orig; } %end %hook UIStatusBarWindow - (void)setStatusBar:(UIStatusBar *)statusBar { if (self.windowScene.screen._isExternal) { statusBar.statusBar.targetScreen = self.windowScene.screen; } %orig; } %end /* %hook SBSystemShellExtendedDisplayControllerPolicy -(void)displayController:(id)arg1 didBeginTransaction:(id)arg2 sceneManager:(id)arg3 displayConfiguration:(id)arg4 deactivationReasons:(unsigned long long)arg5 { forcePadIdiom++; %orig; forcePadIdiom--; } %end */ // Enable Medusa multitasking (three-dots) button on top %hook SBFullScreenSwitcherLiveContentOverlayCoordinator -(void)layoutStateTransitionCoordinator:(id)arg1 transitionDidBeginWithTransitionContext:(id)arg2 { forcePadIdiom++; %orig; forcePadIdiom--; } %end /* %hook SBShelfLiveContentOverlayCoordinator -(void)layoutStateTransitionCoordinator:(id)arg1 transitionDidBeginWithTransitionContext:(id)arg2 { forcePadIdiom++; %orig; forcePadIdiom--; } %end */ // Fix iOS 16 multitasking (split screen, slide over, stage manager) %hook SBMainSwitcherControllerCoordinator - (void)_loadContentViewControllerIfNecessaryForWindowScene:(id)scene { forcePadIdiom++; %orig; forcePadIdiom--; } %end // Fix iOS 18 app switcher animation %hook SBSwitcherController - (void)_updateContentViewControllerIfNeeded { forcePadIdiom++; %orig; forcePadIdiom--; } %end // Min width and height are 150, smaller may crash the app %hook SBSwitcherChamoisLayoutAttributes - (void)setGridWidths:(NSArray *)values { NSUInteger maxValue = values.lastObject.unsignedIntValue; NSMutableArray *array = [NSMutableArray array]; for (int i = 150; i < maxValue; i += 20) { [array addObject:@(i)]; } [array addObject:@(maxValue)]; %orig(array); } - (void)setGridHeights:(NSArray *)values { NSUInteger maxValue = values.lastObject.unsignedIntValue; NSMutableArray *array = [NSMutableArray array]; for (int i = 150; i < maxValue; i += 20) { [array addObject:@(i)]; } [array addObject:@(maxValue)]; %orig(array); } %end // Override app limit, I don't think this is healthy for battery, so I won't make it unlimited... %hook SBSwitcherChamoisSettings - (NSUInteger)maximumNumberOfAppsOnStage { return 5; } %end // FIXME: Is this needed? %hook SBTraitsPipelineManager -(id)defaultOrientationAnimationSettingsAnimatable:(BOOL)animatable { forcePadIdiom++; id result = %orig; forcePadIdiom--; return result; } %end %hook SBTraitsSceneParticipantDelegate // Allow upside down - (BOOL)_isAllowedToHavePortraitUpsideDown { return YES; } // Fix orientation issue for portrait-only apps - (NSInteger)_orientationMode { forcePadIdiom++; NSInteger result = %orig; forcePadIdiom--; return result; } %end // Workaround for iPhones with home button not being able to open Control Center %hook CCSControlCenterDefaults - (NSUInteger)_defaultPresentationGesture { return 1; } %end %hook SBHomeGestureSettings - (BOOL)isHomeGestureEnabled { return YES; } %end %hook SBControlCenterController -(NSUInteger)presentingEdge { return 1; } %end // Forcibly enable resizable as iOS somehow disabled it in the external display %hook SBFluidSwitcherItemContainer - (void)setAllowedTouchResizeCorners:(NSUInteger)cornerMask { // !self.isResizingAllowed && if (self._screen != UIScreen.mainScreen) { %orig(0b1111); // 1100: enable resizing for bottoms // 1111: enable resizing for all corners } else { %orig; } } %end %hook SBAppResizeGrabberView - (void)setAlpha:(CGFloat)alpha { %orig; self.hidden = pref.hideStageManagerResizeCorners; } %end %hook SBFluidSwitcherViewController // Use iPadOS app switching animation instead - (BOOL)isDevicePad { return pref.useiPadAppSwitchingAnimation; } // Restore number of grid to 1 /* - (NSUInteger)numberOfRowsInGridSwitcher { return 1; } */ %end // Fix truncated app name in app switcher %hook SBAppSwitcherSettings - (void)setDefaultValues { %orig; self.spacingBetweenLeadingEdgeAndIcon = 0; self.spacingBetweenTrailingEdgeAndLabels = 0; } %end %hook UIApplication - (id)_defaultSupportedInterfaceOrientations { forcePadIdiom++; id result = %orig; forcePadIdiom--; return result; } %end // Allow upside down Home Screen %hook SBHomeScreenViewController - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return %orig | UIInterfaceOrientationMaskPortraitUpsideDown; } %end // Allow upside down Lock Screen %hook SBCoverSheetPrimarySlidingViewController - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return %orig | UIInterfaceOrientationMaskPortraitUpsideDown; } %end // Pass true to supportAppSceneRequests %hook UISApplicationInitializationContext - (id)initWithMainDisplayContext:(id)arg1 launchDisplayContext:(id)arg2 deviceContext:(id)arg3 persistedSceneIdentifiers:(id)arg4 supportAppSceneRequests:(BOOL)arg5 { return %orig(arg1, arg2, arg3, arg4, YES); } %end // The following hooks are taken from various sources, please refer to tweaks that enable Slide Over. %hook SpringBoard - (NSInteger)homeScreenRotationStyle { return pref.allowLandscapeHomeScreen ? 1 : %orig; } %end %hook SBMedusaConfigurationUsageMetric - (BOOL)_isFloatingActive { return YES; } %end %hook SBPlatformController - (BOOL)isHomeGestureEnabled { return YES; } - (NSInteger)medusaCapabilities { return 2; } %end %hook SBApplication - (BOOL)isMedusaCapable { return pref.forceEnableMedusaForLandscapeOnlyApps || (self.info.supportedInterfaceOrientations & UIInterfaceOrientationMaskPortrait) != 0; } - (BOOL)_supportsApplicationType:(int)arg1 { return YES; } %end %hook SBMainWorkspace - (BOOL)isMedusaEnabled { return YES; } %end %hook SBFloatingDockController + (BOOL)isFloatingDockSupported { return pref.isFloatingDockSupported ? YES : %orig; } %end // Force iPad app switcher, otherwise it will be broken %hook SBAppSwitcherSettings - (NSInteger)effectiveSwitcherStyle { return 2; } // Scales the grid switcher - (void)setGridSwitcherPageScale:(CGFloat)arg1 { return pref.scaleGridSwitcher ? %orig(0.38) : %orig; } - (void)setGridSwitcherVerticalNaturalSpacingPortrait:(CGFloat)arg1 { return pref.scaleGridSwitcher ? %orig(65) : %orig; } - (void)setGridSwitcherVerticalNaturalSpacingLandscape:(CGFloat)arg1 { return pref.scaleGridSwitcher ? %orig(40) : %orig; } - (void)setGridSwitcherHorizontalInterpageSpacingPortrait:(CGFloat)arg1 { return pref.scaleGridSwitcher ? %orig(30) : %orig; } - (void)setGridSwitcherHorizontalInterpageSpacingLandscape:(CGFloat)arg1 { return pref.scaleGridSwitcher ? %orig(10) : %orig; } %end // Unlock external display support for MDC versions int hookedExtDisplayEnabledFunc() { // clang forgets to PAC this function, so we need this ugly line int hack = 0; if (hack) { printf(""); } return 1; } // Bypass Keyboard & Mouse requirement %hook SBExternalDisplayRuntimeAvailabilitySettings - (void)setDefaultValues { self.requireHardwareKeyboard = NO; self.requirePointer = NO; } %end BOOL MGGetBoolAnswer(NSString* property); %hookf(BOOL, MGGetBoolAnswer, NSString* property) { // Hook ipad, DeviceSupportsEnhancedMultitasking if ([property isEqualToString:@"DeviceSupportsEnhancedMultitasking"]) { return YES; } return %orig; } %ctor { // Unlock external display support for MDC versions void *sbFoundationHandle = dlopen("/System/Library/PrivateFrameworks/SpringBoardFoundation.framework/SpringBoardFoundation", RTLD_GLOBAL); // iOS 16.0 void *extDisplayEnabledFunc = dlsym(sbFoundationHandle, "SBChamoisExternalDisplayControllerIsEnabled"); if (!extDisplayEnabledFunc) { // iOS 16.1.x extDisplayEnabledFunc = dlsym(sbFoundationHandle, "SBFIsChamoisExternalDisplayControllerAvailable"); } if (extDisplayEnabledFunc) { MSHookFunction((void *)extDisplayEnabledFunc, (void *)hookedExtDisplayEnabledFunc, NULL); } pref = [TPPrefsObserver new]; } ================================================ FILE: TweakUI.x ================================================ #import "UIKitPrivate.h" #import static BOOL forcePadKBIdiom = YES, showShortcutButtonsOnKeyboard; // Unlock iPadOS keyboard UIUserInterfaceIdiom UIKeyboardGetSafeDeviceIdiom(); %hookf(UIUserInterfaceIdiom, UIKeyboardGetSafeDeviceIdiom) { return forcePadKBIdiom ? UIUserInterfaceIdiomPad : %orig; } // Allow UIHoverGestureRecognizer and pointer interaction on iPhone %hook UIPointerInteraction - (void)_updateInteractionIsEnabled { UIView *view = self.view; BOOL enabled = self.enabled; // && view.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad for(id<_UIPointerInteractionDriver> driver in self.drivers) { driver.view = enabled ? view : nil; } // to keep it fast, ivar offset is cached for later direct access static ptrdiff_t ivarOff = 0; if(!ivarOff) { ivarOff = ivar_getOffset(class_getInstanceVariable(self.class, "_observingPresentationNotification")); } BOOL *observingPresentationNotification = (BOOL *)((uint64_t)(__bridge void *)self + ivarOff); if(!enabled && *observingPresentationNotification) { [NSNotificationCenter.defaultCenter removeObserver:self name:UIPresentationControllerPresentationTransitionWillBeginNotification object:nil]; *observingPresentationNotification = NO; } } %end // Fix bottom padding %hook UIKeyboardImpl + (UIEdgeInsets)deviceSpecificPaddingForInterfaceOrientation:(NSUInteger)arg1 inputMode:(id)arg2 { forcePadKBIdiom = NO; UIEdgeInsets result = %orig; forcePadKBIdiom = YES; return result; } %end // Fix bottom padding when floating %hook UIKeyboardDockView - (CGRect)bounds { CGRect bounds = %orig; if (!UIDevice._hasHomeButton && UIKeyboardImpl.isFloating) { bounds.origin.y = -25; } else { bounds.origin.y = 0; } return bounds; } %end %hook UISystemInputAssistantViewController // Fix predictive bar not occupying entire area - (CGFloat)_centerViewWidthForTraitCollection:(id)tc interfaceOrientation:(UIInterfaceOrientation)orientation { forcePadKBIdiom = NO; NSInteger result = %orig; forcePadKBIdiom = YES; return result; } // Show assistant buttons when enabled - (void)setInputAssistantButtonItemsForResponder:(id)item { forcePadKBIdiom = showShortcutButtonsOnKeyboard; %orig; forcePadKBIdiom = YES; } %end %hook UIInputWindowControllerHosting - (UIEdgeInsets)_inputViewPadding { UIEdgeInsets result = %orig; if (!UIDevice._hasHomeButton && UIKeyboardImpl.isFloating) { result.bottom -= 25; } return result; } %end static void loadPrefs() { NSMutableDictionary *settings = [[NSMutableDictionary alloc] initWithContentsOfFile:@(ROOT_PATH("/var/mobile/Library/Preferences/com.kdt.trollpad.plist"))]; showShortcutButtonsOnKeyboard = [[settings objectForKey:@"TPShowShortcutButtonsOnKeyboard"] boolValue]; } %ctor { loadPrefs(); CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)loadPrefs, CFSTR("com.kdt.trollpad/saved"), NULL, CFNotificationSuspensionBehaviorCoalesce); } ================================================ FILE: UIKitPrivate.h ================================================ #import extern NSNotificationName UIPresentationControllerPresentationTransitionWillBeginNotification; @protocol _UIPointerInteractionDriver @property (assign, nonatomic) UIView *view; @end @interface UIDevice(private) + (BOOL)_hasHomeButton; @end @interface UIKeyboardImpl : NSObject + (BOOL)isFloating; @end @interface UIPointerInteraction(private) - (NSArray > *)drivers; @end @interface UIScreen(private) - (BOOL)isUserInterfaceIdiomPad; - (BOOL)_isExternal; - (void)_setUserInterfaceIdiom:(UIUserInterfaceIdiom)idiom; @end @interface UIStatusBarWindow : UIWindow @end @interface _UIStatusBar - (void)setTargetScreen:(UIScreen *)screen; - (UIScreen *)targetScreen; - (UIScreen *)_effectiveTargetScreen; @end @interface UIStatusBar - (_UIStatusBar *)statusBar; @end @interface UIView(private) - (UIScreen *)_screen; @end ================================================ FILE: control ================================================ Package: com.kdt.trollpad Name: TrollPad Version: 1.3 Architecture: iphoneos-arm Description: Troll SpringBoard into thinking it's running on iPadOS. Maintainer: khanhduytran0 Author: khanhduytran0 Section: Tweaks Depends: mobilesubstrate (>= 0.9.5000), firmware (>= 14.0), plistbuddy Depiction: https://khanhduytran0.github.io/repo/depictions/web/?p=com.kdt.trollpad SileoDepiction: https://khanhduytran0.github.io/repo/depictions/native/com.kdt.trollpad/depiction.json ================================================ FILE: layout/DEBIAN/postinst ================================================ #!/bin/sh set -e jb_root_path=$(realpath $HOME/../..) bundle_system=/System/Library/ControlCenter/Bundles/ContinuousExposeModule.bundle bundle_jb=$jb_root_path/Library/ControlCenter/Bundles/ContinuousExposeModule.bundle plistbuddy=$jb_root_path/usr/libexec/PlistBuddy case "$1" in (configure) if [ -d $bundle_system ] ; then # Copy Stage Manager CC module ln -sf $bundle_system/* $bundle_jb/ rm $bundle_jb/Info.plist cp $bundle_system/Info.plist $bundle_jb/Info.plist $plistbuddy -c "Delete :SBIconVisibilityDefaultVisible" $bundle_jb/Info.plist $plistbuddy -c "Delete :SBIconVisibilitySetByAppPreference" $bundle_jb/Info.plist $plistbuddy -c "Add :UIDeviceFamily:0 integer 1" $bundle_jb/Info.plist fi ;; (abort-upgrade|abort-remove|abort-deconfigure) exit 0 ;; (*) echo "postinst called with unknown argument \`$1'" >&2 exit 0 ;; esac exit 0 ================================================ FILE: layout/DEBIAN/postrm ================================================ #!/bin/sh set -e jb_root_path=$(realpath $HOME/../..) bundle_jb=$jb_root_path/Library/ControlCenter/Bundles/ContinuousExposeModule.bundle case "$1" in (remove) rm -rf $bundle_jb ;; esac exit 0 ================================================ FILE: layout/Library/ControlCenter/Bundles/ContinuousExposeModule.bundle/.keep ================================================