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 <UIKit/UIKit.h>
@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 <Foundation/Foundation.h>
@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 <Preferences/PSListController.h>
#import <Preferences/PSSpecifier.h>
@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 <Foundation/Foundation.h>
// 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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>TrollPad</string>
<key>CFBundleIdentifier</key>
<string>com.kdt.trollpad</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string>TPPRootListController</string>
</dict>
</plist>
================================================
FILE: TrollPadPrefs/Resources/Root.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>label</key>
<string>Stage Manager</string>
</dict>
<dict>
<key>cell</key>
<string>PSLinkListCell</string>
<key>detail</key>
<string>DBSMultitaskingContinuousExposeController</string>
<key>id</key>
<string>CONTINUOUS-EXPOSE</string>
<key>label</key>
<string>Stage Manager</string>
</dict>
<dict>
<key>action</key>
<string>openDisplayArrangement</string>
<key>cell</key>
<string>PSButtonCell</string>
<key>id</key>
<string>DISPLAY_ARRANGEMENT</string>
<key>label</key>
<string>Display Arrangement (doesn't work?)</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>SBExtendedDisplayOverrideSupportForAirPlayAndDontFileRadars</string>
<key>label</key>
<string>Use AirPlay as External Display</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPHideStageManagerResizeCorners</string>
<key>label</key>
<string>Hide resize corners</string>
</dict>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>label</key>
<string>External Display content scale</string>
</dict>
<dict>
<key>cell</key>
<string>PSSliderCell</string>
<key>default</key>
<real>1</real>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>SBExtendedDisplayContentsScaleAndDontFileRadars</string>
<key>max</key>
<real>3</real>
<key>min</key>
<real>1</real>
<key>showValue</key>
<true/>
</dict>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>label</key>
<string>Miscellaneous</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.kdt.trollpad</string>
<key>key</key>
<string>TPShowShortcutButtonsOnKeyboard</string>
<key>label</key>
<string>Show shortcut buttons on keyboard</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPForceEnableMedusaForLandscapeOnlyApps</string>
<key>label</key>
<string>Force resizable window for games</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPUseiPadAppSwitchingAnimation</string>
<key>label</key>
<string>iPadOS app switching animation</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPAllowLandscapeHomeScreen</string>
<key>label</key>
<string>Allow landscape Home Screen</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<true/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPIsFloatingDockSupported</string>
<key>label</key>
<string>Floating Dock</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<true/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>id</key>
<string>SHOW_APP_LIBRARY</string>
<key>key</key>
<string>SBAppLibraryInDockEnabled</string>
<key>label</key>
<string>Show App Library in Dock</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<true/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>id</key>
<string>ALLOW_RECENTS</string>
<key>key</key>
<string>SBRecentsEnabled</string>
<key>label</key>
<string>Show Recents in Dock</string>
</dict>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.apple.springboard</string>
<key>key</key>
<string>TPScaleGridSwitcher</string>
<key>label</key>
<string>Scale Grid Switcher</string>
</dict>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>label</key>
<string>About me</string>
</dict>
<dict>
<key>action</key>
<string>openSourceCode</string>
<key>cell</key>
<string>PSButtonCell</string>
<key>icon</key>
<string>GitHub.png</string>
<key>label</key>
<string>khanhduytran0/TrollPad</string>
</dict>
<dict>
<key>action</key>
<string>openTwitter</string>
<key>cell</key>
<string>PSButtonCell</string>
<key>icon</key>
<string>Twitter.png</string>
<key>label</key>
<string>@TranKha50277352</string>
</dict>
</array>
<key>title</key>
<string>TrollPad</string>
</dict>
</plist>
================================================
FILE: TrollPadPrefs/TPPRootListController.h
================================================
#import <Preferences/PSListController.h>
@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 <Foundation/Foundation.h>
#import <Preferences/PSSpecifier.h>
#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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>entry</key>
<dict>
<key>bundle</key>
<string>TrollPad</string>
<key>cell</key>
<string>PSLinkCell</string>
<key>detail</key>
<string>TPPRootListController</string>
<key>icon</key>
<string>Icon.png</string>
<key>isController</key>
<true/>
<key>label</key>
<string>TrollPad</string>
</dict>
</dict>
</plist>
================================================
FILE: TrollPadPrefs/libfeatureflags+hook.x
================================================
#import <string.h>
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 <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "SpringBoard.h"
#import "TPPrefsObserver.h"
#import "UIKitPrivate.h"
#include <assert.h>
#include <dlfcn.h>
#include <stdbool.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
// #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<NSNumber *> *)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<NSNumber *> *)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 <rootless.h>
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 <UIKit/UIKit.h>
extern NSNotificationName UIPresentationControllerPresentationTransitionWillBeginNotification;
@protocol _UIPointerInteractionDriver<NSObject>
@property (assign, nonatomic) UIView *view;
@end
@interface UIDevice(private)
+ (BOOL)_hasHomeButton;
@end
@interface UIKeyboardImpl : NSObject
+ (BOOL)isFloating;
@end
@interface UIPointerInteraction(private)
- (NSArray <id<_UIPointerInteractionDriver>> *)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
================================================
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
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
{
"path": ".github/workflows/build.yml",
"chars": 1032,
"preview": "name: Theos CI\n\non:\n push:\n pull_request:\n\njobs:\n build:\n\n runs-on: macos-14\n\n steps:\n - uses: actions/check"
},
{
"path": ".gitignore",
"chars": 28,
"preview": ".theos/\npackages/\n.DS_Store\n"
},
{
"path": "Makefile",
"chars": 606,
"preview": "ARCHS := arm64 arm64e\nTARGET := iphone:clang:16.5:15.0\nINSTALL_TARGET_PROCESSES = SpringBoard\n\ninclude $(THEOS)/makefile"
},
{
"path": "README.md",
"chars": 784,
"preview": "# TrollPad\nTroll SpringBoard into thinking it's running on iPadOS\n\n## Features\n- Grid app switcher\n- Multitasking button"
},
{
"path": "SpringBoard.h",
"chars": 767,
"preview": "#import <UIKit/UIKit.h>\n\n@interface SBApplicationInfo : NSObject\n@property(assign, nonatomic) UIInterfaceOrientationMask"
},
{
"path": "TPPrefsObserver.h",
"chars": 282,
"preview": "#import <Foundation/Foundation.h>\n\n@interface TPPrefsObserver : NSObject\n@property(nonatomic, assign) BOOL allowLandscap"
},
{
"path": "TPPrefsObserver.m",
"chars": 1510,
"preview": "#import \"TPPrefsObserver.h\"\n\n@implementation TPPrefsObserver\n- (instancetype)init {\n self = [super init];\n [self o"
},
{
"path": "TrollPadPrefs/DBSMultitaskingContinuousExposeController+hook.x",
"chars": 728,
"preview": "#import <Preferences/PSListController.h>\n#import <Preferences/PSSpecifier.h>\n\n@interface DBSMultitaskingContinuousExpose"
},
{
"path": "TrollPadPrefs/Makefile",
"chars": 407,
"preview": "include $(THEOS)/makefiles/common.mk\n\nBUNDLE_NAME = TrollPad\n\n$(BUNDLE_NAME)_FILES = DBSMultitaskingContinuousExposeCont"
},
{
"path": "TrollPadPrefs/NSUserDefaults+hook.x",
"chars": 1409,
"preview": "#import <Foundation/Foundation.h>\n\n// Hook this to avoid real keys from being set\n%hook NSUserDefaults\n- (void)setBool:("
},
{
"path": "TrollPadPrefs/Resources/Info.plist",
"chars": 730,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollPadPrefs/Resources/Root.plist",
"chars": 5298,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollPadPrefs/TPPRootListController.h",
"chars": 412,
"preview": "#import <Preferences/PSListController.h>\n\n@interface TPPRootListController : PSListController\n@end\n\n// Respring\n@interfa"
},
{
"path": "TrollPadPrefs/TPPRootListController.m",
"chars": 1592,
"preview": "#import <Foundation/Foundation.h>\n#import <Preferences/PSSpecifier.h>\n#import \"TPPRootListController.h\"\n\n#define PREF_PA"
},
{
"path": "TrollPadPrefs/layout/Library/PreferenceLoader/Preferences/TrollPad.plist",
"chars": 509,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollPadPrefs/libfeatureflags+hook.x",
"chars": 291,
"preview": "#import <string.h>\n\nbool _os_feature_enabled_impl(const char *domain, const char *feature);\n%hookf(bool, _os_feature_ena"
},
{
"path": "TrollPadSB.plist",
"chars": 104,
"preview": "{\n Filter = {\n Bundles = (\n \"com.apple.springboard\"\n );\n };\n}"
},
{
"path": "TrollPadUI.plist",
"chars": 98,
"preview": "{\n Filter = {\n Bundles = (\n \"com.apple.UIKit\"\n );\n };\n}"
},
{
"path": "TweakSB.x",
"chars": 11071,
"preview": "#import <Foundation/Foundation.h>\n#import <objc/runtime.h>\n#import \"SpringBoard.h\"\n#import \"TPPrefsObserver.h\"\n#import \""
},
{
"path": "TweakUI.x",
"chars": 3109,
"preview": "#import \"UIKitPrivate.h\"\n#import <rootless.h>\n\nstatic BOOL forcePadKBIdiom = YES, showShortcutButtonsOnKeyboard;\n\n// Unl"
},
{
"path": "UIKitPrivate.h",
"chars": 891,
"preview": "#import <UIKit/UIKit.h>\n\nextern NSNotificationName UIPresentationControllerPresentationTransitionWillBeginNotification;\n"
},
{
"path": "control",
"chars": 471,
"preview": "Package: com.kdt.trollpad\nName: TrollPad\nVersion: 1.3\nArchitecture: iphoneos-arm\nDescription: Troll SpringBoard into thi"
},
{
"path": "layout/DEBIAN/postinst",
"chars": 871,
"preview": "#!/bin/sh\n\nset -e\njb_root_path=$(realpath $HOME/../..)\nbundle_system=/System/Library/ControlCenter/Bundles/ContinuousExp"
},
{
"path": "layout/DEBIAN/postrm",
"chars": 199,
"preview": "#!/bin/sh\n\nset -e\njb_root_path=$(realpath $HOME/../..)\nbundle_jb=$jb_root_path/Library/ControlCenter/Bundles/ContinuousE"
},
{
"path": "layout/Library/ControlCenter/Bundles/ContinuousExposeModule.bundle/.keep",
"chars": 0,
"preview": ""
}
]
About this extraction
This page contains the full source code of the khanhduytran0/TrollPad GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (32.4 KB), approximately 9.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.