New Cycript versions do not toll-free bridge well.
[cydget.git] / CydgetLoader.mm
1 /* Cydget - open-source AwayView plugin multiplexer
2 * Copyright (C) 2009-2015 Jay Freeman (saurik)
3 */
4
5 /* GNU General Public License, Version 3 {{{ */
6 /*
7 * Cydget is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published
9 * by the Free Software Foundation, either version 3 of the License,
10 * or (at your option) any later version.
11 *
12 * Cydget is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Cydget. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 #include <CydiaSubstrate/CydiaSubstrate.h>
23
24 #include <Foundation/Foundation.h>
25 #include <UIKit/UIKit.h>
26
27 #import <SpringBoard/SBAwayController.h>
28 #import <SpringBoard/SBAwayView.h>
29 #import <SpringBoard/SBAwayWindow.h>
30 #import <SpringBoard/SpringBoard.h>
31
32 #include "Handle.hpp"
33
34 #define _trace() \
35 NSLog(@"_trace(%s:%u)@%s %zd", __FILE__, __LINE__, __FUNCTION__, active_)
36 #define _not(type) \
37 static_cast<type>(~type())
38
39 typedef void *GSEventRef;
40
41 extern "C" void UIKeyboardEnableAutomaticAppearance();
42 extern "C" void UIKeyboardDisableAutomaticAppearance();
43
44 MSClassHook(SpringBoard)
45
46 MSClassHook(SBAwayController)
47 MSClassHook(SBAwayView)
48 MSClassHook(SBAwayWindow)
49 MSClassHook(SBLockScreenManager)
50 MSClassHook(SBLockScreenView)
51 MSClassHook(SBLockScreenViewController)
52 MSClassHook(SBLockScreenViewControllerBase)
53 MSClassHook(SBScreenFadeAnimationController)
54 MSClassHook(SBUserAgent)
55
56 MSInitialize {
57 if (kCFCoreFoundationVersionNumber < 800) {
58 $SBUserAgent = $SBAwayController;
59 $SBLockScreenManager = $SBAwayController;
60 $SBLockScreenView = $SBAwayView;
61 $SBLockScreenViewControllerBase = $SBAwayController;
62 }
63 }
64
65 @interface SBLockScreenNowPlayingPluginController : NSObject
66 - (BOOL) isNowPlayingPluginActive;
67 @end
68
69 @interface SBLockScreenViewController : UIViewController
70 - (SBLockScreenView *) lockScreenView;
71 - (BOOL) isShowingMediaControls;
72 - (void) _toggleMediaControls;
73 @end
74
75 @interface SBLockScreenManager : NSObject
76 + (SBLockScreenManager *) sharedInstance;
77 - (SBLockScreenViewController *) lockScreenViewController;
78 @end
79
80 static bool menu_;
81
82 static _H<NSDictionary> settings_;
83 static _H<NSMutableArray> cydgets_;
84 static size_t active_;
85 static unsigned online_;
86 static bool nowplaying_;
87 static bool remember_;
88
89 static bool NowPlaying() {
90 if (kCFCoreFoundationVersionNumber < 800)
91 return false;
92 if (!nowplaying_)
93 return false;
94
95 SBLockScreenViewController *controller([[$SBLockScreenManager sharedInstance] lockScreenViewController]);
96 if (controller != nil) {
97 SBLockScreenNowPlayingPluginController *now(MSHookIvar<SBLockScreenNowPlayingPluginController *>(controller, "_nowPlayingController"));
98 if (now != nil && [now isNowPlayingPluginActive])
99 return true;
100 }
101
102 return false;
103 }
104
105 @interface CydgetController : NSObject {
106 }
107
108 + (NSDictionary *) currentConfiguration;
109 + (NSString *) currentPath;
110
111 @end
112
113 @implementation CydgetController
114
115 + (NSDictionary *) currentConfiguration {
116 NSDictionary *cydget([cydgets_ objectAtIndex:active_]);
117 return [cydget objectForKey:@"CYConfiguration"] ?: [cydget objectForKey:@"Configuration"];
118 }
119
120 + (NSDictionary *) currentPath {
121 NSDictionary *cydget([cydgets_ objectAtIndex:active_]);
122 return [cydget objectForKey:@"CYPath"];
123 }
124
125 @end
126
127 @interface SBUserAgent : NSObject
128 + (SBUserAgent *) sharedUserAgent;
129 - (void) enableLockScreenBundleNamed:(NSString *)bundle activationContext:(id)context;
130 - (void) disableLockScreenBundleNamed:(NSString *)bundle deactivationContext:(id)context;
131 @end
132
133 @interface UIPeripheralHost : NSObject
134 + (UIPeripheralHost *) sharedInstance;
135 + (void) _releaseSharedInstance;
136 @end
137
138 MSClassHook(UIPeripheralHost)
139
140 @interface UITextEffectsWindow : UIWindow
141 + (UIWindow *) sharedTextEffectsWindow;
142 @end
143
144 @implementation NSDictionary (CydgetLoader)
145
146 - (NSString *) cydget {
147 return [self objectForKey:@"CYPlugin"] ?: [self objectForKey:@"Plugin"];
148 }
149
150 - (void) enableCydget {
151 if (NSString *plugin = [self cydget]) {
152 ++online_;
153
154 if (kCFCoreFoundationVersionNumber < 600)
155 UIKeyboardEnableAutomaticAppearance();
156
157 [[UITextEffectsWindow sharedTextEffectsWindow] setWindowLevel:1000];
158
159 if (kCFCoreFoundationVersionNumber < 800)
160 [[$SBAwayController sharedAwayController] enableLockScreenBundleWithName:plugin];
161 else
162 [[$SBUserAgent sharedUserAgent] enableLockScreenBundleNamed:plugin activationContext:nil];
163 }
164 }
165
166 - (void) disableCydget {
167 if (NSString *plugin = [self cydget]) {
168 if (kCFCoreFoundationVersionNumber < 800)
169 [[$SBAwayController sharedAwayController] disableLockScreenBundleWithName:plugin];
170 else
171 [[$SBUserAgent sharedUserAgent] disableLockScreenBundleNamed:plugin deactivationContext:nil];
172
173 [$UIPeripheralHost _releaseSharedInstance];
174
175 if (kCFCoreFoundationVersionNumber < 600)
176 UIKeyboardDisableAutomaticAppearance();
177
178 --online_;
179 }
180 }
181
182 @end
183
184 // avoid rendering a keyboard onto the default SBAwayView while automatic keyboard is online
185 MSInstanceMessageHook0(UIView *, SBAwayView, inputView) {
186 // XXX: there is a conceptual error here
187 if (online_ == 0 && false || kCFCoreFoundationVersionNumber > 600)
188 return MSOldCall();
189
190 return [[[UIView alloc] init] autorelease];
191 }
192
193 // by default, keyboard actions are redirected to SBAwayController and press menu button
194 MSInstanceMessageHook1(void, SpringBoard, handleKeyEvent, GSEventRef, event) {
195 if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
196 return MSOldCall(event);
197
198 return MSSuperCall(event);
199 }
200
201 bool media_;
202
203 MSInstanceMessageHook0(void, SBLockScreenViewController, _toggleMediaControls) {
204 if (!media_)
205 MSOldCall();
206 }
207
208 MSInstanceMessageHook0(BOOL, SBLockScreenViewController, handleMenuButtonDoubleTap) {
209 menu_ = false;
210 BOOL value(MSOldCall());
211 if (kCFCoreFoundationVersionNumber >= 800)
212 [self _toggleMediaControls];
213 return value;
214 }
215
216 MSInstanceMessageHook0(BOOL, SBLockScreenViewControllerBase, handleMenuButtonTap) {
217 media_ = true;
218 BOOL value(MSOldCall());
219 media_ = false;
220
221 if (!value && menu_) {
222 if (active_ != _not(size_t))
223 [[cydgets_ objectAtIndex:active_] disableCydget];
224 active_ = (active_ + 1) % [cydgets_ count];
225 [[cydgets_ objectAtIndex:active_] enableCydget];
226 // XXX: or siri doesn't disappear correctly
227 return kCFCoreFoundationVersionNumber >= 800 ? value : YES;
228 }
229
230 return value;
231 }
232
233 void Activate_() {
234 menu_ = false;
235 if (NowPlaying()) {
236 for (active_ = 0; active_ != [cydgets_ count]; ++active_)
237 if ([[[cydgets_ objectAtIndex:active_] objectForKey:@"CYName"] isEqualToString:@"AwayView"])
238 break;
239 if (active_ == [cydgets_ count])
240 active_ = _not(size_t);
241 }
242 if (active_ != _not(size_t))
243 [[cydgets_ objectAtIndex:active_] enableCydget];
244 }
245
246 static void Undim_(SBAwayController *self) {
247 if ([self isDimmed]) {
248 Activate_();
249 [[[self awayView] window] makeKeyWindow];
250 }
251 }
252
253 MSInstanceMessageHook1(void, SBLockScreenViewController, startLockScreenFadeInAnimationForSource, int, source) {
254 Activate_();
255 MSOldCall(source);
256 }
257
258 MSInstanceMessageHook0(void, SBAwayController, undimScreen) {
259 Undim_(self);
260 MSOldCall();
261 }
262
263 MSInstanceMessageHook1(void, SBAwayController, undimScreen, BOOL, undim) {
264 Undim_(self);
265 MSOldCall(undim);
266 }
267
268 static void Deactivate_() {
269 if (active_ != _not(size_t))
270 [[cydgets_ objectAtIndex:active_] disableCydget];
271 if (!remember_)
272 active_ = 0;
273 }
274
275 MSHook(void, BKSDisplayServicesSetScreenBlanked, BOOL blanked) {
276 if (blanked)
277 Deactivate_();
278 _BKSDisplayServicesSetScreenBlanked(blanked);
279 }
280
281 MSInitialize {
282 MSHookFunction("_BKSDisplayServicesSetScreenBlanked", MSHake(BKSDisplayServicesSetScreenBlanked));
283 }
284
285 MSInstanceMessageHook1(void, SBUserAgent, dimScreen, BOOL, dim) {
286 Deactivate_();
287 MSOldCall(dim);
288 }
289
290 MSInstanceMessageHook0(void, SpringBoard, _menuButtonWasHeld) {
291 menu_ = false;
292 MSOldCall();
293 }
294
295 MSInstanceMessageHook1(void, SpringBoard, _menuButtonDown, GSEventRef, event) {
296 menu_ = true;
297 MSOldCall(event);
298 }
299
300 MSInstanceMessageHook1(void, SpringBoard, menuButtonDown, GSEventRef, event) {
301 menu_ = true;
302 MSOldCall(event);
303 }
304
305 MSInstanceMessageHook1(void, SBAwayView, addGestureRecognizer, id, recognizer) {
306 if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
307 return MSOldCall(recognizer);
308 }
309
310 MSInstanceMessageHook1(void, SBAwayWindow, sendGSEvent, GSEventRef, event) {
311 if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
312 return MSOldCall(event);
313
314 return MSSuperCall(event);
315 }
316
317 MSInstanceMessageHook2(void, SBLockScreenView, setMediaControlsHidden,forRequester, BOOL, hidden, id, requester) {
318 MSOldCall(hidden, requester);
319 UIView *&_mediaControlsContainerView(MSHookIvar<UIView *>(self, "_mediaControlsContainerView"));
320 UIView *&_mediaControlsView(MSHookIvar<UIView *>(self, "_mediaControlsView"));
321 if (&_mediaControlsContainerView != NULL && &_mediaControlsView != NULL)
322 [_mediaControlsContainerView setUserInteractionEnabled:([_mediaControlsView alpha] != 0)];
323 }
324
325 #define Cydgets_ @"/System/Library/LockCydgets"
326
327 static void CydgetSetup() {
328 NSString *plist([NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.Cydget.plist", NSHomeDirectory()]);
329 settings_ = [NSMutableDictionary dictionaryWithContentsOfFile:plist] ?: [NSMutableDictionary dictionary];
330
331 nowplaying_ = [[settings_ objectForKey:@"NowPlaying"] boolValue];
332 remember_ = [[settings_ objectForKey:@"RememberPosition"] boolValue];
333
334 NSArray *cydgets([settings_ objectForKey:@"LockCydgets"] ?: [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:
335 @"Welcome", @"Name", [NSNumber numberWithBool:YES], @"Active", nil
336 ], [NSDictionary dictionaryWithObjectsAndKeys:
337 @"AwayView", @"Name", [NSNumber numberWithBool:YES], @"Active", nil
338 ], nil]);
339
340 cydgets_ = [NSMutableArray arrayWithCapacity:4];
341 for (NSDictionary *cydget in cydgets)
342 if ([[cydget objectForKey:@"Active"] boolValue]) {
343 NSString *name([cydget objectForKey:@"Name"]);
344 NSString *path([NSString stringWithFormat:@"%@/%@.cydget/Info.plist", Cydgets_, name]);
345 if (NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:path]) {
346 [info setObject:name forKey:@"CYName"];
347 [info setObject:path forKey:@"CYPath"];
348 [cydgets_ addObject:info];
349 }
350 }
351
352 if ([cydgets_ count] == 0)
353 cydgets_ = nil;
354 }
355
356 // XXX: this could happen while it is unlocked
357 MSInstanceMessageHook1(id, SBLockScreenView, initWithFrame, CGRect, frame) {
358 self = MSOldCall(frame);
359
360 static bool loaded(false);
361 if (!loaded) {
362 loaded = true;
363 CydgetSetup();
364 Activate_();
365 }
366
367 // XXX: this is just wrong, I don't know what to do :(
368 else if (kCFCoreFoundationVersionNumber < 800)
369 Activate_();
370
371 return self;
372 }
373
374 MSInstanceMessageHook1(void, SBAwayController, _finishedUnlockAttemptWithStatus, BOOL, status) {
375 if (status)
376 Deactivate_();
377 MSOldCall(status);
378 }
379
380 // this is called occasionally by -[SBAwayView updateInterface] and takes away our keyboard
381 MSInstanceMessageHook0(void, SBAwayView, _fixupFirstResponder) {
382 if (online_ == 0)
383 return MSOldCall();
384 }
385
386 MSInstanceMessageHook0(void, SBAwayView, updateInterface) {
387 MSOldCall();
388
389 NSDictionary *cydget([cydgets_ objectAtIndex:active_]);
390
391 NSString *background([cydget objectForKey:@"CYBackground"]);
392 if ([background isEqualToString:@"Wallpaper"]) {
393 MSIvarHook(UIView *, _backgroundView);
394 [_backgroundView setAlpha:1.0f];
395 }
396
397 if ([[cydget objectForKey:@"CYShowDateTime"] boolValue])
398 [self addDateView];
399 }