1 /* Cydia Substrate - Powerful Code Insertion Platform
2 * Copyright (C) 2008-2013 Jay Freeman (saurik)
5 /* GNU Lesser General Public License, Version 3 {{{ */
7 * Substrate is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
12 * Substrate is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Substrate. If not, see <http://www.gnu.org/licenses/>.
22 #import <CoreFoundation/CoreFoundation.h>
23 #import <Foundation/Foundation.h>
24 #import <CoreGraphics/CGGeometry.h>
25 #import <UIKit/UIKit.h>
27 #include <substrate.h>
29 Class $SafeModeAlertItem;
31 @interface SBAlertItem : NSObject {
33 - (UIAlertView *) alertSheet;
37 @interface SBAlertItemsController : NSObject {
39 + (SBAlertItemsController *) sharedInstance;
40 - (void) activateAlertItem:(SBAlertItem *)item;
43 @interface SBStatusBarTimeView : UIView {
48 @interface UIApplication (CydiaSubstrate)
49 - (void) applicationOpenURL:(id)url;
52 @interface UIAlertView (CydiaSubstrate)
53 - (void) setForceHorizontalButtonsLayout:(BOOL)force;
54 - (void) setBodyText:(NSString *)body;
55 - (void) setNumberOfRows:(NSInteger)rows;
58 void SafeModeAlertItem$alertSheet$buttonClicked$(id self, SEL sel, id sheet, int button) {
64 if (kCFCoreFoundationVersionNumber >= 700)
65 system("killall backboardd");
67 // XXX: there are better ways of restarting SpringBoard that would actually save state
72 [[UIApplication sharedApplication] applicationOpenURL:[NSURL URLWithString:@"http://cydia.saurik.com/safemode/"]];
79 void SafeModeAlertItem$configure$requirePasscodeForActions$(id self, SEL sel, BOOL configure, BOOL require) {
80 UIAlertView *sheet([self alertSheet]);
82 [sheet setDelegate:self];
83 [sheet setBodyText:@"We apologize for the inconvenience, but SpringBoard has just crashed.\n\nMobileSubstrate /did not/ cause this problem: it has protected you from it.\n\nYour device is now running in Safe Mode. All extensions that support this safety system are disabled.\n\nReboot (or restart SpringBoard) to return to the normal mode. To return to this dialog touch the status bar.\n\nTap \"Help\" below for more tips."];
84 [sheet addButtonWithTitle:@"OK"];
85 [sheet addButtonWithTitle:@"Restart"];
86 [sheet addButtonWithTitle:@"Help"];
87 [sheet setNumberOfRows:1];
89 if ([sheet respondsToSelector:@selector(setForceHorizontalButtonsLayout:)])
90 [sheet setForceHorizontalButtonsLayout:YES];
93 void SafeModeAlertItem$performUnlockAction(id self, SEL sel) {
94 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:self];
97 static void MSAlert() {
98 if ($SafeModeAlertItem == nil)
99 $SafeModeAlertItem = objc_lookUpClass("SafeModeAlertItem");
100 if ($SafeModeAlertItem == nil) {
101 $SafeModeAlertItem = objc_allocateClassPair(objc_getClass("SBAlertItem"), "SafeModeAlertItem", 0);
102 if ($SafeModeAlertItem == nil)
105 class_addMethod($SafeModeAlertItem, @selector(alertSheet:buttonClicked:), (IMP) &SafeModeAlertItem$alertSheet$buttonClicked$, "v@:@i");
106 class_addMethod($SafeModeAlertItem, @selector(configure:requirePasscodeForActions:), (IMP) &SafeModeAlertItem$configure$requirePasscodeForActions$, "v@:cc");
107 class_addMethod($SafeModeAlertItem, @selector(performUnlockAction), (IMP) SafeModeAlertItem$performUnlockAction, "v@:");
108 objc_registerClassPair($SafeModeAlertItem);
111 if (%c(SBAlertItemsController) != nil)
112 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:[[[$SafeModeAlertItem alloc] init] autorelease]];
116 // XXX: on iOS 5.0, we really would prefer avoiding
119 - (void) touchesEnded:(id)touches withEvent:(id)event {
121 %orig(touches, event);
125 - (void) mouseDown:(void *)event {
131 - (void) touchesBegan:(void *)touches withEvent:(void *)event {
133 %orig(touches, event);
137 // this fairly complex code came from Grant, to solve the "it Safe Mode"-in-bar bug
139 %hook SBStatusBarDataManager
140 - (void) _updateTimeString {
141 char *_data(&MSHookIvar<char>(self, "_data"));
145 Ivar _itemIsEnabled(object_getInstanceVariable(self, "_itemIsEnabled", NULL));
146 if (_itemIsEnabled == NULL)
149 Ivar _itemIsCloaked(object_getInstanceVariable(self, "_itemIsCloaked", NULL));
150 if (_itemIsCloaked == NULL)
153 size_t enabledOffset(ivar_getOffset(_itemIsEnabled));
154 size_t cloakedOffset(ivar_getOffset(_itemIsCloaked));
155 if (enabledOffset >= cloakedOffset)
158 size_t offset(cloakedOffset - enabledOffset);
159 char *timeString(_data + offset);
160 strcpy(timeString, "Exit Safe Mode");
164 static bool alerted_;
166 static void AlertIfNeeded() {
174 // on iOS 4.3 and above we can use this advertisement, which seems to check every time the user unlocks
175 // XXX: verify that this still works on iOS 5.0
177 %hook AAAccountManager
178 + (void) showMobileMeOfferIfNecessary {
183 // -[SBIconController showInfoAlertIfNeeded] explains how to drag icons around the iPhone home screen
184 // it used to be shown to users when they unlocked their screen for the first time, and happened every unlock
185 // however, as of iOS 4.3, it got relegated to only appearing once the user installed an app or web clip
187 %hook SBIconController
188 - (void) showInfoAlertIfNeeded {
193 // the icon state, including crazy configurations like Five Icon Dock, is stored in SpringBoard's defaults
194 // unfortunately, SpringBoard on iOS 2.0 and 2.1 (maybe 2.2 as well) buffer overrun with more than 4 icons
195 // there is a third party package called IconSupport that remedies this, but not everyone is using it yet
198 - (int) maxIconColumns {
202 if (NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults])
203 if (NSDictionary *iconState = [defaults objectForKey:@"iconState"])
204 if (NSDictionary *buttonBar = [iconState objectForKey:@"buttonBar"])
205 if (NSArray *iconMatrix = [buttonBar objectForKey:@"iconMatrix"])
206 if ([iconMatrix count] != 0)
207 if (NSArray *row = [iconMatrix objectAtIndex:0]) {
208 int count([row count]);
218 if ((self = %orig()) != nil) {
219 UIView *&_contentLayer(MSHookIvar<UIView *>(self, "_contentLayer"));
220 UIView *&_contentView(MSHookIvar<UIView *>(self, "_contentView"));
223 if (&_contentLayer != NULL)
224 layer = _contentLayer;
225 else if (&_contentView != NULL)
226 layer = _contentView;
231 [layer setBackgroundColor:[UIColor darkGrayColor]];
235 #define Paper_ "/Library/MobileSubstrate/MobileSafety.png"
238 + (UIImage *) defaultDesktopImage {
239 return [UIImage imageWithContentsOfFile:@Paper_];
242 %hook SBStatusBarTimeView
244 NSString *&_time(MSHookIvar<NSString *>(self, "_time"));
245 CGRect &_textRect(MSHookIvar<CGRect>(self, "_textRect"));
248 _time = [@"Exit Safe Mode" retain];
249 id font([self textFont]);
250 CGSize size([_time sizeWithFont:font]);
251 CGRect frame([self frame]);
252 _textRect.size = size;
253 _textRect.origin.x = (frame.size.width - size.width) / 2;
254 _textRect.origin.y = (frame.size.height - size.height) / 2;
258 // notification widgets ("wee apps" or "bulletin board sections") are capable of crashing SpringBoard
259 // unfortunately, which ones are in use are stored in SpringBoard's defaults, so we need to turn them off
262 - (BOOL) showsInNotificationCenter {
267 // on iOS 6.0, Apple split parts of SpringBoard into a daemon called backboardd, including app launches
268 // in order to allow safe mode to propogate into applications, we need to then tell backboardd here
269 // XXX: (all of this should be replaced, however, with per-process launchd-mediated exception handling)
271 %hook BKSApplicationLaunchSettings
272 - (void) setEnvironment:(NSDictionary *)original {
276 NSMutableDictionary *modified([original mutableCopy]);
277 [modified setObject:@"1" forKey:@"_MSSafeMode"];
278 return %orig(modified);
282 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
284 // on iOS 6, backboardd is in charge of brightness, and freaks out when SpringBoard restarts :(
285 // the result is that the device is super dark until we attempt to update the brightness here.
287 if (kCFCoreFoundationVersionNumber >= 700) {
288 if (void (*GSEventSetBacklightLevel)(float) = reinterpret_cast<void (*)(float)>(dlsym(RTLD_DEFAULT, "GSEventSetBacklightLevel")))
289 if (NSMutableDictionary *defaults = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Library/Preferences/com.apple.springboard.plist", NSHomeDirectory()]])
290 if (NSNumber *level = [defaults objectForKey:@"SBBacklightLevel2"])
291 GSEventSetBacklightLevel([level floatValue]);