├── CustomMenu.h ├── CustomMenu.m ├── Makefile ├── README.md ├── Settings.h ├── Settings.m ├── TabBlocker.plist ├── Tweak.xm ├── control └── tabblockerpreferencebundle ├── Makefile ├── Resources ├── Icon-40.png ├── Icon-50.png ├── Icon-App-40x40@2x.png ├── Info.plist ├── Root.plist ├── RoundedIcon.png └── tabicon2.jpg ├── XXXRootListController.h ├── XXXRootListController.m └── entry.plist /CustomMenu.h: -------------------------------------------------------------------------------- 1 | @interface CustomMenu : UIWindow { 2 | UIViewController *controller; 3 | UISegmentedControl *segment; 4 | UILabel *infoLabel; 5 | NSString *url; 6 | NSString *domain; 7 | } 8 | -(void)hideMenu; 9 | -(void)showMenu:(NSString*)cleanURL domain:(NSString*)cleanDomain; 10 | @end -------------------------------------------------------------------------------- /CustomMenu.m: -------------------------------------------------------------------------------- 1 | #import "CustomMenu.h" 2 | #import 3 | 4 | 5 | //needed to compile without iOS 13 SDK 6 | typedef enum UISceneActivationState : NSInteger { 7 | UISceneActivationStateUnattached = -1, 8 | UISceneActivationStateForegroundActive, 9 | UISceneActivationStateForegroundInactive, 10 | UISceneActivationStateBackground 11 | } UISceneActivationState; 12 | 13 | @interface UIScene : NSObject 14 | @property(nonatomic, readonly) UISceneActivationState activationState; 15 | @end 16 | 17 | @interface UIApplication () 18 | -(id)connectedScenes; 19 | @end 20 | 21 | @interface UIWindowScene : UIScene 22 | @end 23 | 24 | @interface UIWindow () 25 | @property(nonatomic, weak) UIWindowScene *windowScene; 26 | @end 27 | 28 | 29 | 30 | // custom view controller to disable landscape rotation of subviews on iOS 13 31 | @interface CustomMenuViewController : UIViewController 32 | @end 33 | @implementation CustomMenuViewController 34 | -(BOOL)shouldAutorotate{ 35 | return NO; 36 | } 37 | @end 38 | 39 | 40 | @implementation CustomMenu 41 | 42 | -(id)init { 43 | CGRect screen = [UIScreen mainScreen].bounds; 44 | self = [super initWithFrame:CGRectMake(10, screen.size.height - 115, screen.size.width - 20, 70)]; 45 | 46 | if (self) { 47 | CGRect menuLocation = [self frame]; 48 | 49 | controller = [[CustomMenuViewController alloc] init]; //use our own ViewController 50 | [self setRootViewController:controller]; 51 | [self setWindowLevel:9998]; 52 | [self setBackgroundColor:[UIColor whiteColor]]; 53 | [self setHidden:YES]; 54 | [self makeKeyAndVisible]; 55 | [self.layer setCornerRadius:8]; 56 | [self.layer setBorderWidth:1]; 57 | [self.layer setBorderColor:[[UIColor blackColor]CGColor]]; 58 | 59 | segment = [[UISegmentedControl alloc] initWithFrame:CGRectMake(15, menuLocation.size.height - 40, menuLocation.size.width - 30, 30)]; 60 | [segment removeAllSegments]; 61 | [segment insertSegmentWithTitle:@"Allow" atIndex:0 animated:NO]; 62 | [segment insertSegmentWithTitle:@"Block URL" atIndex:1 animated:NO]; 63 | [segment insertSegmentWithTitle:@"Block Domain" atIndex:2 animated:NO]; 64 | [segment addTarget:self action:@selector(onButtonTab:) forControlEvents:UIControlEventValueChanged]; 65 | [[UISegmentedControl appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor blackColor]} forState:UIControlStateNormal]; // better readable on iOS 13 66 | 67 | infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, menuLocation.size.height - 70, menuLocation.size.width - 30, 30)]; 68 | [infoLabel setText:@"This webpage wants to open a new tab..."]; 69 | [infoLabel setTextAlignment:NSTextAlignmentCenter]; 70 | [infoLabel setAdjustsFontSizeToFitWidth:YES]; 71 | infoLabel.textColor = [UIColor blackColor];// to make it readable on iOS 13 72 | [self addSubview:segment]; 73 | [self addSubview:infoLabel]; 74 | 75 | [self hideMenu]; 76 | } 77 | return self; 78 | } 79 | 80 | -(void)hideMenu{ 81 | [segment setSelectedSegmentIndex:-1]; 82 | [self setHidden:YES]; 83 | } 84 | 85 | -(void)showMenu:(NSString *)cleanURL domain:(NSString*)cleanDomain{ 86 | [self setHidden:NO]; 87 | 88 | //on iOS 13 UIWindows need a UIWindowScene to show https://stackoverflow.com/questions/57134259/how-to-resolve-keywindow-was-deprecated-in-ios-13-0 89 | if(kCFCoreFoundationVersionNumber >= 1665.15) { 90 | if(!self.windowScene || self.windowScene.activationState != UISceneActivationStateForegroundActive) { 91 | NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes; 92 | for(UIScene *scene in connectedScenes) { 93 | if(scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[NSClassFromString(@"UIWindowScene") class]]) { 94 | self.windowScene = (UIWindowScene *)scene; 95 | break; 96 | } 97 | } 98 | } 99 | } 100 | 101 | url = [[NSString alloc] initWithString:cleanURL]; 102 | domain = [[NSString alloc] initWithString:cleanDomain]; 103 | } 104 | 105 | #define ALLOW_URL 0 106 | #define BLOCK_URL 1 107 | #define BLOCK_DOMAIN 2 108 | 109 | -(void)onButtonTab:(UISegmentedControl *)sender { 110 | if (sender){ 111 | HBPreferences *prefrences = [[HBPreferences alloc] initWithIdentifier:@"com.droomone.tabblocker"]; 112 | switch([segment selectedSegmentIndex]) 113 | { 114 | case ALLOW_URL: 115 | NSLog(@"Adding [%@] to whitelisted domains", domain); 116 | NSString * whitelistdomain = [[prefrences objectForKey:@"whitelistdomain"] stringValue]; 117 | [prefrences setObject:[[NSString alloc] initWithFormat:@"%@;%@", whitelistdomain, domain] forKey:@"whitelistdomain"]; 118 | break; 119 | case BLOCK_DOMAIN: 120 | NSLog(@"Adding [%@] to blocked domains", domain); 121 | NSString * blacklistdomain = [[prefrences objectForKey:@"blacklistdomain"] stringValue]; 122 | [prefrences setObject:[[NSString alloc] initWithFormat:@"%@;%@", blacklistdomain, domain] forKey:@"blacklistdomain"]; 123 | break; 124 | case BLOCK_URL: 125 | NSLog(@"Adding [%@] to blocked URL's", url); 126 | NSString * blacklisturl = [[prefrences objectForKey:@"blacklisturl"] stringValue]; 127 | [prefrences setObject:[[NSString alloc] initWithFormat:@"%@;%@", blacklisturl, url] forKey:@"blacklisturl"]; 128 | break; 129 | } 130 | [self hideMenu]; 131 | } 132 | } 133 | 134 | 135 | 136 | @end -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | INSTALL_TARGET_PROCESSES = MobileSafari 3 | 4 | THEOS_PACKAGE_DIR_NAME = debs 5 | TARGET=iphone:clang 6 | ARCHS= armv7 arm64 arm64e 7 | include $(THEOS)/makefiles/common.mk 8 | TWEAK_NAME = TabBlocker 9 | TabBlocker_FILES = Tweak.xm CustomMenu.m Settings.m 10 | TabBlocker_EXTRA_FRAMEWORKS = Cephei 11 | 12 | include $(THEOS_MAKE_PATH)/tweak.mk 13 | after-install:: 14 | install.exec "killall -9 MobileSafari" 15 | SUBPROJECTS += tabblockerpreferencebundle 16 | include $(THEOS_MAKE_PATH)/aggregate.mk 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TabBlocker 2 | TabBlocker is able to block the opening of new tabs for specified websites. 3 | 4 | Version: 1.2 5 | Depends: mobilesubstrate ws.hbang.common 6 | 7 | Video: https://streamable.com/y43bo 8 | -------------------------------------------------------------------------------- /Settings.h: -------------------------------------------------------------------------------- 1 | @interface Settings : NSObject { 2 | } 3 | -(NSString *)GetGlobalSettingsByKey: (NSString *)key; 4 | -(NSString *)GetLocalSettingsByKey: (NSString *)key; 5 | @end -------------------------------------------------------------------------------- /Settings.m: -------------------------------------------------------------------------------- 1 | #include "Settings.h" 2 | 3 | @implementation Settings 4 | 5 | -(NSString *)GetGlobalSettingsByKey: (NSString *)key{ 6 | NSString * path = [NSString stringWithFormat:@"/var/mobile/Library/Preferences/com.droomone.tabblockerpreferencebundle.plist"]; 7 | NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:path]; 8 | /*url_list*/ NSString * result = [[prefs objectForKey:key] stringValue]; 9 | NSLog(@"Global Settings [%@]: %@", key, result); 10 | return result; 11 | } 12 | -(NSString *)GetLocalSettingsByKey: (NSString *)key{ 13 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 14 | NSString *documentsDirectory = [paths objectAtIndex:0]; 15 | NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:@"tabblocker.plist"]; 16 | NSLog(@"Reading local settings [%@]", documentsPath); 17 | NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:documentsPath]; 18 | NSString * result = [[prefs objectForKey:key] stringValue]; 19 | NSLog(@"Local Settings [%@]: %@", key, result); 20 | return result; 21 | } 22 | 23 | @end -------------------------------------------------------------------------------- /TabBlocker.plist: -------------------------------------------------------------------------------- 1 | { Filter = { Bundles = ( "com.apple.mobilesafari"); }; } 2 | -------------------------------------------------------------------------------- /Tweak.xm: -------------------------------------------------------------------------------- 1 | #import 2 | #import "CustomMenu.h" 3 | #import 4 | 5 | 6 | //- Opzetten omgeving 7 | //- Vinden bronnen (google dorks) 8 | //- basics 9 | // - Logging 10 | // - Vinden van functies 11 | // - Xcode 12 | // - UIMenu popup 13 | // - Maken preference bundle 14 | // - Communiceren met die bundle 15 | 16 | static CustomMenu * menu; 17 | static NSArray * blockedURLs; 18 | static NSArray * blockedDomains; 19 | static NSArray * allowedDomains; 20 | static bool nodialog; 21 | static id tabController; 22 | 23 | @interface TabDocument 24 | -(NSString *)URLString; 25 | -(NSURL *)cachedCanonicalURLOrURLForSharing; 26 | @end 27 | 28 | @interface TabController 29 | @property(strong) CustomMenu *m; // UIWindow needs strong reference to show on iOS 13 30 | -(id)parentTabDocumentForBackClosesSpawnedTab; //method to get originalTab 31 | @end 32 | 33 | 34 | void updatePrefs(){ 35 | // Getting settings from plist 36 | HBPreferences *preference = [[HBPreferences alloc] initWithIdentifier:@"com.droomone.tabblocker"]; 37 | NSString * whitelistdomain = [[preference objectForKey:@"whitelistdomain"] stringValue]; 38 | NSString * blacklisturl = [[preference objectForKey:@"blacklisturl"] stringValue]; 39 | NSString * blacklistdomain = [[preference objectForKey:@"blacklistdomain"] stringValue]; 40 | nodialog = [[preference objectForKey:@"nodialog"] boolValue]; 41 | 42 | // Converting to array's 43 | blockedURLs = [blacklisturl componentsSeparatedByString:@";"]; 44 | blockedDomains = [blacklistdomain componentsSeparatedByString:@";"]; 45 | allowedDomains = [whitelistdomain componentsSeparatedByString:@";"]; 46 | } 47 | 48 | 49 | %group iOS12 50 | %hook TabController 51 | 52 | 53 | 54 | - (void)insertNewTabDocument:(id)arg1 openedFromTabDocument:(id)document inBackground:(_Bool)arg3 animated:(_Bool)arg4{ 55 | updatePrefs(); 56 | TabDocument *originalTab = document; 57 | tabController = self; 58 | 59 | if ([originalTab URLString]){ 60 | 61 | //used to get correctly formatted NSURL; so it's not nil if it contains special characters 62 | //Not tested on iOS 11 63 | NSURL * originURL = [originalTab cachedCanonicalURLOrURLForSharing]; 64 | 65 | // Cleaning URL from safari! 66 | NSString * cleanDomain = [[originURL host] stringByReplacingOccurrencesOfString:@"www." withString:@""]; 67 | NSString * cleanURL = [[originURL resourceSpecifier] stringByReplacingOccurrencesOfString:@"//" withString:@""]; 68 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"http://" withString:@""]; 69 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"https://" withString:@""]; 70 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"www." withString:@""]; 71 | 72 | if ([allowedDomains containsObject:cleanDomain]){ 73 | NSLog(@"Allowed new tab from: [%@] - specified in whitelisted domains", cleanDomain); 74 | return %orig; 75 | } 76 | 77 | if ([blockedDomains containsObject:cleanDomain]){ 78 | NSLog(@"Blocked new tab from: [%@] - specified in blacklisted domains", cleanDomain); 79 | return; 80 | } 81 | 82 | for (id blockedURL in blockedURLs){ 83 | // Cleaning URL input by user 84 | NSString * cleanBlockedURL = [blockedURL stringByReplacingOccurrencesOfString:@"http://" withString:@""]; 85 | cleanBlockedURL = [cleanBlockedURL stringByReplacingOccurrencesOfString:@"https://" withString:@""]; 86 | cleanBlockedURL = [cleanBlockedURL stringByReplacingOccurrencesOfString:@"www." withString:@""]; 87 | 88 | if ([cleanBlockedURL isEqualToString:cleanURL]){ 89 | NSLog(@"Blocked new tab from: [%@] - specified in blacklisted URL's", cleanURL); 90 | return; 91 | } 92 | } 93 | 94 | // Nothing happend, must be a new website! Showing userdialog :) 95 | if (!nodialog) { 96 | [menu showMenu:cleanURL domain:cleanDomain]; 97 | return; 98 | } 99 | } 100 | return %orig; 101 | } 102 | %end 103 | %end 104 | 105 | 106 | 107 | %group iOS13 108 | %hook TabController 109 | // UIWindow needs strong reference to show on iOS 13 110 | %property(strong) CustomMenu *m; 111 | 112 | -(id)initWithBrowserController:(id)arg1{ 113 | self.m = [[CustomMenu alloc] init]; 114 | return %orig; 115 | } 116 | 117 | -(void)insertNewTabDocumentWithDefaultOrdering:(id)arg1 inBackground:(BOOL)arg3 animated:(BOOL)arg4{ 118 | updatePrefs(); 119 | 120 | TabDocument *originalTab = [arg1 parentTabDocumentForBackClosesSpawnedTab]; 121 | tabController = self; 122 | 123 | if ([originalTab URLString]){ 124 | 125 | //used to get correctly formatted NSURL; so it's not nil if it contains special characters 126 | NSURL * originURL = [originalTab cachedCanonicalURLOrURLForSharing]; 127 | 128 | // Cleaning URL from safari! 129 | NSString * cleanDomain = [[originURL host] stringByReplacingOccurrencesOfString:@"www." withString:@""]; 130 | NSString * cleanURL = [[originURL resourceSpecifier] stringByReplacingOccurrencesOfString:@"//" withString:@""]; 131 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"http://" withString:@""]; 132 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"https://" withString:@""]; 133 | cleanURL = [cleanURL stringByReplacingOccurrencesOfString:@"www." withString:@""]; 134 | 135 | if ([allowedDomains containsObject:cleanDomain]){ 136 | NSLog(@"Allowed new tab from: [%@] - specified in whitelisted domains", cleanDomain); 137 | return %orig; 138 | } 139 | 140 | if ([blockedDomains containsObject:cleanDomain]){ 141 | NSLog(@"Blocked new tab from: [%@] - specified in blacklisted domains", cleanDomain); 142 | return; 143 | } 144 | 145 | for (id blockedURL in blockedURLs){ 146 | // Cleaning URL input by user 147 | NSString * cleanBlockedURL = [blockedURL stringByReplacingOccurrencesOfString:@"http://" withString:@""]; 148 | cleanBlockedURL = [cleanBlockedURL stringByReplacingOccurrencesOfString:@"https://" withString:@""]; 149 | cleanBlockedURL = [cleanBlockedURL stringByReplacingOccurrencesOfString:@"www." withString:@""]; 150 | 151 | if ([cleanBlockedURL isEqualToString:cleanURL]){ 152 | NSLog(@"Blocked new tab from: [%@] - specified in blacklisted URL's", cleanURL); 153 | return; 154 | } 155 | } 156 | 157 | // Nothing happend, must be a new website! Showing userdialog :) 158 | if (!nodialog) { 159 | [self.m showMenu:cleanURL domain:cleanDomain]; 160 | return; 161 | } 162 | } 163 | return %orig; 164 | } 165 | %end 166 | %end 167 | 168 | %ctor{ 169 | updatePrefs(); 170 | if(kCFCoreFoundationVersionNumber >= 1665.15){ 171 | %init(iOS13); 172 | }else{ 173 | menu = [[CustomMenu alloc] init]; 174 | %init(iOS12); 175 | } 176 | } -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.droomone.tabblocker 2 | Name: TabBlocker 3 | Depends: mobilesubstrate, ws.hbang.common 4 | Version: 1.2 5 | Architecture: iphoneos-arm 6 | Description: Blocking new advertisement tabs & more 7 | Maintainer: DroomOne 8 | Author: DroomOne 9 | Section: Tweaks 10 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Makefile: -------------------------------------------------------------------------------- 1 | include $(THEOS)/makefiles/common.mk 2 | ARCHS= armv7 arm64 arm64e 3 | 4 | BUNDLE_NAME = TabBlockerPreferenceBundle 5 | TabBlockerPreferenceBundle_FILES = XXXRootListController.m 6 | TabBlockerPreferenceBundle_INSTALL_PATH = /Library/PreferenceBundles 7 | TabBlockerPreferenceBundle_FRAMEWORKS = UIKit 8 | TabBlockerPreferenceBundle_PRIVATE_FRAMEWORKS = Preferences 9 | 10 | include $(THEOS_MAKE_PATH)/bundle.mk 11 | 12 | internal-stage:: 13 | $(ECHO_NOTHING)mkdir -p $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences$(ECHO_END) 14 | $(ECHO_NOTHING)cp entry.plist $(THEOS_STAGING_DIR)/Library/PreferenceLoader/Preferences/TabBlockerPreferenceBundle.plist$(ECHO_END) 15 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroomOne/TabBlocker/4d7853d9ad81b56257e36ec8714939cf549de5eb/tabblockerpreferencebundle/Resources/Icon-40.png -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/Icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroomOne/TabBlocker/4d7853d9ad81b56257e36ec8714939cf549de5eb/tabblockerpreferencebundle/Resources/Icon-50.png -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroomOne/TabBlocker/4d7853d9ad81b56257e36ec8714939cf549de5eb/tabblockerpreferencebundle/Resources/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | TabBlockerPreferenceBundle 9 | CFBundleIdentifier 10 | com.droomone.tabblockerpreferencebundle 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | NSPrincipalClass 22 | XXXRootListController 23 | 24 | 25 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | Title 11 | URL's to filter 12 | label 13 | Blacklisted URL's (splitted by ;) 14 | 15 | 16 | cell 17 | PSEditTextCell 18 | defaults 19 | com.droomone.tabblocker 20 | detail 21 | PSDetailController 22 | placeholder 23 | http:// 24 | key 25 | blacklisturl 26 | 27 | 28 | cell 29 | PSGroupCell 30 | Title 31 | Domains's to filter 32 | label 33 | Blacklisted Domains (splitted by ;) 34 | 35 | 36 | cell 37 | PSEditTextCell 38 | defaults 39 | com.droomone.tabblocker 40 | detail 41 | PSDetailController 42 | placeholder 43 | http:// 44 | key 45 | blacklistdomain 46 | 47 | 48 | cell 49 | PSGroupCell 50 | Title 51 | URL's to filter 52 | label 53 | Whitelisted Domains's (splitted by ;) 54 | 55 | 56 | cell 57 | PSEditTextCell 58 | defaults 59 | com.droomone.tabblocker 60 | detail 61 | PSDetailController 62 | placeholder 63 | http:// 64 | key 65 | whitelistdomain 66 | 67 | 68 | cell 69 | PSGroupCell 70 | label 71 | Settings 72 | 73 | 74 | cell 75 | PSSwitchCell 76 | default 77 | 78 | defaults 79 | com.droomone.tabblocker 80 | key 81 | nodialog 82 | label 83 | Disable Menu 84 | 85 | 86 | cell 87 | PSSwitchCell 88 | default 89 | 90 | defaults 91 | com.droomone.tabblocker 92 | key 93 | animation 94 | label 95 | Disable Tab Animation 96 | 97 | 98 | cell 99 | PSGroupCell 100 | label 101 | Made by DroomOne - /u/Droom0ne 102 | 103 | 104 | title 105 | Tab Blocker 106 | 107 | 108 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/RoundedIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroomOne/TabBlocker/4d7853d9ad81b56257e36ec8714939cf549de5eb/tabblockerpreferencebundle/Resources/RoundedIcon.png -------------------------------------------------------------------------------- /tabblockerpreferencebundle/Resources/tabicon2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroomOne/TabBlocker/4d7853d9ad81b56257e36ec8714939cf549de5eb/tabblockerpreferencebundle/Resources/tabicon2.jpg -------------------------------------------------------------------------------- /tabblockerpreferencebundle/XXXRootListController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface XXXRootListController : PSListController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/XXXRootListController.m: -------------------------------------------------------------------------------- 1 | #include "XXXRootListController.h" 2 | 3 | @implementation XXXRootListController 4 | 5 | - (NSArray *)specifiers { 6 | if (!_specifiers) { 7 | _specifiers = [[self loadSpecifiersFromPlistName:@"Root" target:self] retain]; 8 | } 9 | 10 | return _specifiers; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /tabblockerpreferencebundle/entry.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | TabBlockerPreferenceBundle 9 | cell 10 | PSLinkCell 11 | detail 12 | XXXRootListController 13 | icon 14 | Icon-50.png 15 | isController 16 | 17 | label 18 | Tab Blocker 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------