Avoid modifying stylesheets when updating @medias.
[cydget.git] / LockScreen.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 #include <UIKit/UIKit.h>
24
25 #include <pthread.h>
26
27 #include <SpringBoardUI/SBAwayViewPluginController.h>
28
29 #include <WebKit/WebFrame.h>
30 #include <WebKit/WebView.h>
31 #include <WebKit/WebPreferences-WebPrivate.h>
32
33 #include <WebCycript.h>
34
35 #ifdef USE_ICU_REGEX
36 #include <unicode/uregex.h>
37 #else
38 #include <pcre.h>
39 #endif
40
41 _disused static unsigned trace_;
42
43 #define _trace() do { \
44     NSLog(@"_trace(%u)@%s:%u[%s](%p)\n", \
45         trace_++, __FILE__, __LINE__, __FUNCTION__, pthread_self() \
46     ); \
47 } while (false)
48
49 typedef uint16_t UChar;
50
51 @interface TPBottomLockBar
52 - (CGFloat) defaultHeight;
53 @end
54
55 @interface UIScroller : UIView
56 - (void) setDirectionalScrolling:(BOOL)directional;
57 - (void) setScrollDecelerationFactor:(CGFloat)factor;
58 - (void) setScrollHysteresis:(CGFloat)hysteresis;
59 - (void) setThumbDetectionEnabled:(BOOL)enabled;
60 @end
61
62 @interface UIWebDocumentView : UIView
63 - (CGRect) documentBounds;
64 - (void) enableReachability;
65 - (void) loadRequest:(NSURLRequest *)request;
66 - (void) redrawScaledDocument;
67 - (void) setAllowsImageSheet:(BOOL)allows;
68 - (void) setAllowsMessaging:(BOOL)allows;
69 - (void) setAutoresizes:(BOOL)autoresizes;
70 - (void) setContentsPosition:(NSInteger)position;
71 - (void) setDrawsBackground:(BOOL)draws;
72 - (void) _setDocumentType:(NSInteger)type;
73 - (void) setDrawsGrid:(BOOL)draws;
74 - (void) setInitialScale:(float)scale forDocumentTypes:(NSInteger)types;
75 - (void) setLogsTilingChanges:(BOOL)logs;
76 - (void) setMinimumScale:(float)scale forDocumentTypes:(NSInteger)types;
77 - (void) setMinimumSize:(CGSize)size;
78 - (void) setMaximumScale:(float)scale forDocumentTypes:(NSInteger)tpyes;
79 - (void) setSmoothsFonts:(BOOL)smooths;
80 - (void) setTileMinificationFilter:(NSString *)filter;
81 - (void) setTileSize:(CGSize)size;
82 - (void) setTilingEnabled:(BOOL)enabled;
83 - (void) setViewportSize:(CGSize)size forDocumentTypes:(NSInteger)types;
84 - (void) setZoomsFocusedFormControl:(BOOL)zooms;
85 - (void) useSelectionAssistantWithMode:(NSInteger)mode;
86 - (WebView *) webView;
87 @end
88
89 @interface UIView (Apple)
90 - (UIScroller *) _scroller;
91 - (void) setClipsSubviews:(BOOL)clips;
92 - (void) setEnabledGestures:(NSInteger)gestures;
93 - (void) setFixedBackgroundPattern:(BOOL)fixed;
94 - (void) setGestureDelegate:(id)delegate;
95 - (void) setNeedsDisplayOnBoundsChange:(BOOL)needs;
96 - (void) setValue:(NSValue *)value forGestureAttribute:(NSInteger)attribute;
97 - (void) setZoomScale:(float)scale duration:(double)duration;
98 - (void) _setZoomScale:(float)scale duration:(double)duration;
99 @end
100
101 @interface SBLockScreenView : UIView
102 - (BOOL) mediaControlsHidden;
103 @end
104
105 @interface SBLockScreenNotificationListController : NSObject
106 - (BOOL) hasAnyContent;
107 @end
108
109 @interface SBLockScreenViewController : UIViewController
110 - (SBLockScreenView *) lockScreenView;
111 - (BOOL) isShowingMediaControls;
112 - (void) _setMediaControlsVisible:(BOOL)visible;
113 - (SBLockScreenNotificationListController *) _notificationController;
114 @end
115
116 @interface SBLockScreenManager : NSObject
117 + (SBLockScreenManager *) sharedInstance;
118 - (SBLockScreenViewController *) lockScreenViewController;
119 @end
120
121 @protocol CydgetController
122 - (NSDictionary *) currentConfiguration;
123 - (NSString *) currentPath;
124 @end
125
126 static Class $CydgetController(objc_getClass("CydgetController"));
127
128 MSClassHook(SBLockScreenManager)
129
130 #ifdef USE_ICU_REGEX
131 /* ICU Regular Expression {{{ */
132 class RegEx {
133   private:
134     URegularExpression *regex_;
135
136   public:
137     RegEx(const char *regex) {
138         UParseError error;
139         UErrorCode status(U_ZERO_ERROR);
140         regex_ = uregex_openC(regex, 0, &error, &status);
141         if (U_FAILURE(status))
142             @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"*** RegEx(): [%u] %s", error.offset, u_errorName(status)] userInfo:nil];
143     }
144
145     ~RegEx() {
146         uregex_close(regex_);
147     }
148
149     bool operator ()(NSString *string) {
150         return operator ()(reinterpret_cast<const uint16_t *>([string cStringUsingEncoding:NSUTF16StringEncoding]), [string length]);
151     }
152
153     bool operator ()(const UChar *data, size_t size) {
154         UErrorCode status(U_ZERO_ERROR);
155         uregex_setText(regex_, data, size, &status);
156         if (!U_SUCCESS(status))
157             return false;
158         bool matches(uregex_matches(regex_, 0, &status));
159         if (!U_SUCCESS(status))
160             return false;
161         return matches;
162     }
163 };
164 /* }}} */
165 #else
166 /* Perl-Compatible RegEx {{{ */
167 class RegEx {
168   private:
169     pcre *code_;
170     pcre_extra *study_;
171     int capture_;
172     int *matches_;
173     const char *data_;
174
175   public:
176     RegEx(const char *regex) :
177         study_(NULL)
178     {
179         const char *error;
180         int offset;
181         code_ = pcre_compile(regex, 0, &error, &offset, NULL);
182
183         if (code_ == NULL)
184             @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"*** RegEx(): [%u] %s", offset, error] userInfo:nil];
185
186         pcre_fullinfo(code_, study_, PCRE_INFO_CAPTURECOUNT, &capture_);
187         matches_ = new int[(capture_ + 1) * 3];
188     }
189
190     ~RegEx() {
191         pcre_free(code_);
192         delete matches_;
193     }
194
195     bool operator ()(NSString *data) {
196         // XXX: length is for characters, not for bytes
197         return operator ()([data UTF8String], [data length]);
198     }
199
200     bool operator ()(const char *data, size_t size) {
201         data_ = data;
202         return pcre_exec(code_, study_, data, size, 0, 0, matches_, (capture_ + 1) * 3) >= 0;
203     }
204 };
205 /* }}} */
206 #endif
207
208 static float CYScrollViewDecelerationRateNormal;
209
210 @interface UIScrollView (Apple)
211 - (void) setDecelerationRate:(CGFloat)value;
212 - (void) setScrollingEnabled:(BOOL)enabled;
213 - (void) setShowBackgroundShadow:(BOOL)show;
214 @end
215
216 @interface UIWebView (Apple)
217 - (UIWebDocumentView *) _documentView;
218 - (void) setDataDetectorTypes:(NSInteger)types;
219 - (void) _setDrawInWebThread:(BOOL)draw;
220 - (UIScrollView *) _scrollView;
221 - (UIScroller *) _scroller;
222 - (void) webView:(WebView *)view addMessageToConsole:(NSDictionary *)message;
223 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame;
224 @end
225
226 @interface WebView (Apple)
227 - (void) _setLayoutInterval:(float)interval;
228 - (void) _setAllowsMessaging:(BOOL)allows;
229 - (void) setShouldUpdateWhileOffscreen:(BOOL)update;
230 @end
231
232 @protocol CydgetWebViewDelegate <UIWebViewDelegate>
233 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame;
234 @end
235
236 @class UIWebViewWebViewDelegate;
237
238 @interface UIWebView (WebCycript)
239 - (void) updateStyles;
240 @end
241
242 @interface CydgetWebView : UIWebView {
243 }
244
245 @end
246
247 @implementation CydgetWebView
248
249 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
250     NSObject<CydgetWebViewDelegate> *delegate((NSObject<CydgetWebViewDelegate> *) [self delegate]);
251     if ([delegate respondsToSelector:@selector(webView:didClearWindowObject:forFrame:)])
252         [delegate webView:view didClearWindowObject:window forFrame:frame];
253     if ([UIWebView instancesRespondToSelector:@selector(webView:didClearWindowObject:forFrame:)])
254         [super webView:view didClearWindowObject:window forFrame:frame];
255 }
256
257 - (void) webView:(WebView *)view addMessageToConsole:(NSDictionary *)message {
258     NSLog(@"addMessageToConsole:%@", message);
259
260     if ([UIWebView instancesRespondToSelector:@selector(webView:addMessageToConsole:)])
261         [super webView:view addMessageToConsole:message];
262 }
263
264 @end
265
266 @interface WebCydgetLockScreenView : UIView <UIWebViewDelegate> {
267     CydgetWebView *webview_;
268     UIScrollView *scroller_;
269     NSString *cycript_;
270 }
271
272 - (void) updateStyles;
273
274 @end
275
276 @implementation WebCydgetLockScreenView
277
278 //#include "UICaboodle/UCInternal.h"
279
280 - (void) dealloc {
281     [[NSNotificationCenter defaultCenter] removeObserver:self];
282     [webview_ setDelegate:nil];
283     [webview_ release];
284     [super dealloc];
285 }
286
287 - (void) loadRequest:(NSURLRequest *)request {
288     [webview_ loadRequest:request];
289 }
290
291 - (void) loadURL:(NSURL *)url cachePolicy:(NSURLRequestCachePolicy)policy {
292     [self loadRequest:[NSURLRequest
293         requestWithURL:url
294         cachePolicy:policy
295         timeoutInterval:30.0
296     ]];
297 }
298
299 - (void) loadURL:(NSURL *)url {
300     [self loadURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy];
301 }
302
303 - (id) initWithURL:(NSURL *)url {
304     CGRect frame = [[UIScreen mainScreen] bounds];
305     if (kCFCoreFoundationVersionNumber < 800)
306         frame.size.height -= 20; //[[[$SBStatusBarController sharedStatusBarController] statusBarView] frame].size.height;
307
308     if ((self = [super initWithFrame:frame]) != nil) {
309         CGRect bounds([self bounds]);
310         if (kCFCoreFoundationVersionNumber < 800)
311             bounds.size.height -= [objc_getClass("TPBottomLockBar") defaultHeight];
312
313         webview_ = [[CydgetWebView alloc] initWithFrame:bounds];
314         [webview_ setDelegate:self];
315         [self addSubview:webview_];
316
317         if ([webview_ respondsToSelector:@selector(setDataDetectorTypes:)])
318             [webview_ setDataDetectorTypes:0x80000000];
319         else
320             [webview_ setDetectsPhoneNumbers:NO];
321
322         [webview_ setScalesPageToFit:YES];
323
324         if (kCFCoreFoundationVersionNumber < 478.61)
325             if ([webview_ respondsToSelector:@selector(_setDrawInWebThread:)])
326                 [webview_ _setDrawInWebThread:NO];
327
328         UIWebDocumentView *document([webview_ _documentView]);
329         WebView *webview([document webView]);
330         WebPreferences *preferences([webview preferences]);
331
332         [document setTileSize:CGSizeMake(bounds.size.width, 500)];
333
334         [document setBackgroundColor:[UIColor clearColor]];
335         [document setDrawsBackground:NO];
336
337         [webview setPreferencesIdentifier:@"WebCycript"];
338
339         if ([webview respondsToSelector:@selector(_setLayoutInterval:)])
340             [webview _setLayoutInterval:0];
341         else
342             [preferences _setLayoutInterval:0];
343
344         [preferences setCacheModel:WebCacheModelDocumentViewer];
345         [preferences setJavaScriptCanOpenWindowsAutomatically:YES];
346         [preferences setOfflineWebApplicationCacheEnabled:YES];
347
348         if ([webview respondsToSelector:@selector(setShouldUpdateWhileOffscreen:)])
349             [webview setShouldUpdateWhileOffscreen:NO];
350
351         if ([document respondsToSelector:@selector(setAllowsMessaging:)])
352             [document setAllowsMessaging:YES];
353         if ([webview respondsToSelector:@selector(_setAllowsMessaging:)])
354             [webview _setAllowsMessaging:YES];
355
356         if ([webview_ respondsToSelector:@selector(_scrollView)]) {
357             scroller_ = [webview_ _scrollView];
358
359             [scroller_ setDirectionalLockEnabled:YES];
360             [scroller_ setDecelerationRate:CYScrollViewDecelerationRateNormal];
361             [scroller_ setDelaysContentTouches:NO];
362
363             [scroller_ setCanCancelContentTouches:YES];
364
365             [scroller_ setAlwaysBounceVertical:NO];
366         } else if ([webview_ respondsToSelector:@selector(_scroller)]) {
367             UIScroller *scroller([webview_ _scroller]);
368             scroller_ = (UIScrollView *) scroller;
369
370             [scroller setDirectionalScrolling:YES];
371             [scroller setScrollDecelerationFactor:CYScrollViewDecelerationRateNormal]; /* 0.989324 */
372             [scroller setScrollHysteresis:0]; /* 8 */
373
374             [scroller setThumbDetectionEnabled:NO];
375         }
376
377         [webview_ setOpaque:NO];
378         [webview_ setBackgroundColor:[UIColor clearColor]];
379
380         [scroller_ setFixedBackgroundPattern:YES];
381         [scroller_ setBackgroundColor:[UIColor clearColor]];
382         [scroller_ setClipsSubviews:NO];
383
384         [scroller_ setBounces:YES];
385         [scroller_ setShowBackgroundShadow:NO]; /* YES */
386
387         [self setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
388         [webview_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
389
390         NSDictionary *configuration([$CydgetController currentConfiguration]);
391         cycript_ = [configuration objectForKey:@"CycriptURLs"];
392
393         [scroller_ setScrollingEnabled:[[configuration objectForKey:@"Scrollable"] boolValue]];
394
395         [self loadURL:url];
396
397         [[NSNotificationCenter defaultCenter]
398             addObserver:self
399             selector:@selector(mediaControlsDidSomething:)
400             name:@"SBLockScreenViewControllerMediaControlsDidShow"
401             object:nil
402         ];
403
404         [[NSNotificationCenter defaultCenter]
405             addObserver:self
406             selector:@selector(mediaControlsDidSomething:)
407             name:@"SBLockScreenViewControllerMediaControlsDidHide"
408             object:nil
409         ];
410     } return self;
411 }
412
413 - (void) mediaControlsDidSomething:(NSNotification *)notification {
414     [self updateStyles];
415 }
416
417 - (void) webView:(WebView *)webview didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
418     if (cycript_ != nil)
419         if (NSString *href = [[[[frame dataSource] request] URL] absoluteString])
420             if (RegEx([cycript_ UTF8String])(href))
421                 WebCycriptSetupView(webview);
422 }
423
424 - (void) updateStyles {
425     [webview_ updateStyles];
426 }
427
428 @end
429
430 @interface WebCycriptLockScreenController : SBAwayViewPluginController {
431     NSDictionary *configuration_;
432     bool legacy_;
433     bool media_;
434     bool items_;
435     WebCydgetLockScreenView *background_;
436     WebCydgetLockScreenView *foreground_;
437 }
438
439 @end
440
441 static BOOL CYHaveMediaControls() {
442     SBLockScreenView *view([[[$SBLockScreenManager sharedInstance] lockScreenViewController] lockScreenView]);
443     return view != nil && ![view mediaControlsHidden];
444     //return [[[$SBLockScreenManager sharedInstance] lockScreenViewController] isShowingMediaControls];
445 }
446
447 static BOOL CYHaveNotificationList() {
448     SBLockScreenNotificationListController *controller([[[$SBLockScreenManager sharedInstance] lockScreenViewController] _notificationController]);
449     return controller != nil && [controller hasAnyContent];
450 }
451
452 static void $UIWebViewWebViewDelegate$webView$addMessageToConsole$(UIWebViewWebViewDelegate *self, SEL sel, WebView *view, NSDictionary *message) {
453     UIWebView *uiWebView(MSHookIvar<UIWebView *>(self, "uiWebView"));
454     if ([uiWebView respondsToSelector:@selector(webView:addMessageToConsole:)])
455         [uiWebView webView:view addMessageToConsole:message];
456 }
457
458 static void $UIWebViewWebViewDelegate$webView$didClearWindowObject$forFrame$(UIWebViewWebViewDelegate *self, SEL sel, WebView *view, WebScriptObject *window, WebFrame *frame) {
459     UIWebView *uiWebView(MSHookIvar<UIWebView *>(self, "uiWebView"));
460     if ([uiWebView respondsToSelector:@selector(webView:didClearWindowObject:forFrame:)])
461         [uiWebView webView:view didClearWindowObject:window forFrame:frame];
462 }
463
464 MSInitialize {
465     if (Class $UIWebViewWebViewDelegate = objc_getClass("UIWebViewWebViewDelegate")) {
466         class_addMethod($UIWebViewWebViewDelegate, @selector(webView:addMessageToConsole:), (IMP) &$UIWebViewWebViewDelegate$webView$addMessageToConsole$, "v16@0:4@8@12");
467         class_addMethod($UIWebViewWebViewDelegate, @selector(webView:didClearWindowObject:forFrame:), (IMP) &$UIWebViewWebViewDelegate$webView$didClearWindowObject$forFrame$, "v20@0:4@8@12@16");
468     }
469
470     if (CGFloat *_UIScrollViewDecelerationRateNormal = reinterpret_cast<CGFloat *>(dlsym(RTLD_DEFAULT, "UIScrollViewDecelerationRateNormal")))
471         CYScrollViewDecelerationRateNormal = *_UIScrollViewDecelerationRateNormal;
472     else // XXX: this actually might be fast on some older systems: we should look into this
473         CYScrollViewDecelerationRateNormal = 0.998;
474
475     if (kCFCoreFoundationVersionNumber >= 800) {
476         WebCycriptRegisterStyle("-cydget-media-controls", &CYHaveMediaControls);
477         WebCycriptRegisterStyle("-cydget-notification-list", &CYHaveNotificationList);
478     }
479 }
480
481 @implementation WebCycriptLockScreenController
482
483 + (id) rootViewController {
484     return [[[self alloc] init] autorelease];
485 }
486
487 - (void) dealloc {
488     [configuration_ release];
489     [background_ release];
490     [foreground_ release];
491     [super dealloc];
492 }
493
494 - (id) init {
495     if ((self = [super init]) != nil) {
496         configuration_ = [[$CydgetController currentConfiguration] retain];
497         legacy_ = [configuration_ objectForKey:@"Background"] == nil;
498         media_ = [[configuration_ objectForKey:@"MediaControls"] boolValue];
499         items_ = [[configuration_ objectForKey:@"NotificationList"] boolValue];
500     } return self;
501 }
502
503 - (void) loadView {
504     NSURL *base([NSURL fileURLWithPath:[$CydgetController currentPath]]);
505
506     if (NSString *background = [configuration_ objectForKey:@"Background"])
507         background_ = [[WebCydgetLockScreenView alloc] initWithURL:[NSURL URLWithString:background relativeToURL:base]];
508
509     if (NSString *homepage = [configuration_ objectForKey:@"Homepage"]) {
510         foreground_ = [[WebCydgetLockScreenView alloc] initWithURL:[NSURL URLWithString:homepage relativeToURL:base]];
511         [self setView:foreground_];
512     }
513 }
514
515 - (void) purgeView {
516     [background_ removeFromSuperview];
517     [background_ release];
518     background_ = nil;
519     [foreground_ removeFromSuperview];
520     [foreground_ release];
521     foreground_ = nil;
522     [super purgeView];
523 }
524
525 - (void) updateStyles {
526     [foreground_ updateStyles];
527     [background_ updateStyles];
528 }
529
530 - (UIView *) backgroundView {
531     return background_;
532 }
533
534 - (BOOL) showAwayItems {
535     return YES;
536 }
537
538 - (BOOL) updateHidden {
539     if (foreground_ == nil)
540         return true;
541     bool media(CYHaveMediaControls());
542     [foreground_ setHidden:media];
543     return media;
544 }
545
546 - (BOOL) showDateView {
547     if (kCFCoreFoundationVersionNumber < 800)
548         return false;
549     [self updateStyles];
550     if (!legacy_ && foreground_ == nil)
551         return true;
552     if (!items_ && CYHaveNotificationList())
553         return true;
554     return false;
555 }
556
557 - (BOOL) allowsLockScreenMediaControls {
558     if (kCFCoreFoundationVersionNumber < 800)
559         return true;
560     return media_;
561 }
562
563 /*- (BOOL) showHeaderView {
564     return YES;
565 }*/
566
567 // 0: view is rendered above head
568 // 1: view moves as one with head
569 // 2: view moves up and down only
570 // 3: view simply never does move
571 - (NSUInteger) presentationStyle {
572     return 1;
573 }
574
575 // 1: light blur
576 // 2: heavy blur
577 // 3: just black
578 // 4: legibility
579 // 5: no overlay
580 - (NSUInteger) overlayStyle {
581     return legacy_ ? 1 : 4;
582 }
583
584 // 1: blur -> view -> list
585 // 2: view -> blur -> list
586 // 3: view. unblur below?!
587 // 4: blur -> list -> view
588 - (NSUInteger) notificationBehavior {
589     return items_ ? 1 : 2;
590 }
591
592 - (BOOL) viewWantsFullscreenLayout {
593     return kCFCoreFoundationVersionNumber >= 800;
594 }
595
596 /*- (BOOL) viewWantsOverlayLayout {
597     return kCFCoreFoundationVersionNumber >= 800;
598 }*/
599
600 - (BOOL) shouldDisableOnUnlock {
601     return YES;
602 }
603
604 - (BOOL) canBeAlwaysFullscreen {
605     return YES;
606 }
607
608 /*- (BOOL) wantsSwipeGestureRecognizer {
609     return YES;
610 }
611
612 - (BOOL) handleGesture:(int)arg1 fingerCount:(NSUInteger)fingers {
613     return NO;
614     return YES;
615 }*/
616
617 // - (void) lockScreenMediaControlsShown:(BOOL)shown;
618
619 - (BOOL) handleMenuButtonDoubleTap {
620     if (kCFCoreFoundationVersionNumber >= 800) {
621         SBLockScreenViewController *controller([[$SBLockScreenManager sharedInstance] lockScreenViewController]);
622         [controller _setMediaControlsVisible:![controller isShowingMediaControls]];
623     }
624
625     return [super handleMenuButtonDoubleTap];
626 }
627
628 @end