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