Fix menu button dim/undim logic on iOS 6.
[cydget.git] / Tweak.mm
1 /* Cydget - open-source AwayView plugin multiplexer
2  * Copyright (C) 2009-2013  Jay Freeman (saurik)
3 */
4
5 /*
6  *        Redistribution and use in source and binary
7  * forms, with or without modification, are permitted
8  * provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the
11  *    above copyright notice, this list of conditions
12  *    and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the
14  *    above copyright notice, this list of conditions
15  *    and the following disclaimer in the documentation
16  *    and/or other materials provided with the
17  *    distribution.
18  * 3. The name of the author may not be used to endorse
19  *    or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
27  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #include <substrate.h>
39
40 #include <Foundation/Foundation.h>
41 #include <GraphicsServices/GraphicsServices.h>
42 #include <UIKit/UIKit.h>
43
44 #import <SpringBoard/SBAwayController.h>
45 #import <SpringBoard/SBAwayView.h>
46 #import <SpringBoard/SBAwayWindow.h>
47 #import <SpringBoard/SpringBoard.h>
48
49 #include "Handle.hpp"
50
51 MSClassHook(SpringBoard)
52
53 MSClassHook(SBAwayController)
54 MSClassHook(SBAwayView)
55 MSClassHook(SBAwayViewController)
56 MSClassHook(SBAwayWindow)
57
58 #define _trace() \
59     NSLog(@"_trace(%s:%u)@%s %d", __FILE__, __LINE__, __FUNCTION__, active_)
60 #define _not(type) \
61     static_cast<type>(~type())
62
63 static bool menu_;
64
65 static _H<NSArray> settings_;
66 static _H<NSArray> cydgets_;
67 static size_t active_;
68 static unsigned online_;
69
70 @interface CydgetController : NSObject {
71 }
72
73 + (NSDictionary *) currentConfiguration;
74
75 @end
76
77 @implementation CydgetController
78
79 + (NSDictionary *) currentConfiguration {
80     NSDictionary *cydget([cydgets_ objectAtIndex:active_]);
81     return [cydget objectForKey:@"CYConfiguration"] ?: [cydget objectForKey:@"Configuration"];
82 }
83
84 @end
85
86 @interface NSDictionary (Cydgets)
87 - (void) enableCydget:(SBAwayController *)away;
88 - (void) disableCydget:(SBAwayController *)away;
89 @end
90
91 @interface UIPeripheralHost : NSObject
92 + (UIPeripheralHost *) sharedInstance;
93 + (void) _releaseSharedInstance;
94 @end
95
96 MSClassHook(UIPeripheralHost)
97
98 @interface UITextEffectsWindow : UIWindow
99 + (UIWindow *) sharedTextEffectsWindow;
100 @end
101
102 @implementation NSDictionary (Cydgets)
103
104 - (void) enableCydget:(SBAwayController *)away {
105     if (NSString *plugin = [self objectForKey:@"CYPlugin"] ?: [self objectForKey:@"Plugin"]) {
106         ++online_;
107
108         if (kCFCoreFoundationVersionNumber < 600)
109             UIKeyboardEnableAutomaticAppearance();
110
111         [[UITextEffectsWindow sharedTextEffectsWindow] setWindowLevel:1000];
112         [away enableLockScreenBundleWithName:plugin];
113     }
114 }
115
116 - (void) disableCydget:(SBAwayController *)away {
117     if (NSString *plugin = [self objectForKey:@"CYPlugin"] ?: [self objectForKey:@"Plugin"]) {
118         [away disableLockScreenBundleWithName:plugin];
119         [$UIPeripheralHost _releaseSharedInstance];
120
121         if (kCFCoreFoundationVersionNumber < 600)
122             UIKeyboardDisableAutomaticAppearance();
123
124         --online_;
125     }
126 }
127
128 @end
129
130 // avoid rendering a keyboard onto the default SBAwayView while automatic keyboard is online
131 MSInstanceMessageHook0(UIView *, SBAwayView, inputView) {
132     // XXX: there is a conceptual error here
133     if (online_ == 0 && false || kCFCoreFoundationVersionNumber > 600)
134         return MSOldCall();
135
136     return [[[UIView alloc] init] autorelease];
137 }
138
139 // by default, keyboard actions are redirected to SBAwayController and press menu button
140 MSInstanceMessageHook1(void, SpringBoard, handleKeyEvent, GSEventRef, event) {
141     if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
142         return MSOldCall(event);
143
144     return MSSuperCall(event);
145 }
146
147 MSInstanceMessageHook0(BOOL, SBAwayController, handleMenuButtonTap) {
148     if (!MSOldCall() && menu_) {
149         [[cydgets_ objectAtIndex:active_] disableCydget:self];
150         active_ = (active_ + 1) % [cydgets_ count];
151         [[cydgets_ objectAtIndex:active_] enableCydget:self];
152     }
153
154     return YES;
155 }
156
157 static void Undim_(SBAwayController *self) {
158     if ([self isDimmed]) {
159         menu_ = false;
160         [[cydgets_ objectAtIndex:active_] enableCydget:self];
161         [[[self awayView] window] makeKeyWindow];
162     }
163 }
164
165 MSInstanceMessageHook0(void, SBAwayController, undimScreen) {
166     Undim_(self);
167     MSOldCall();
168 }
169
170 MSInstanceMessageHook1(void, SBAwayController, undimScreen, BOOL, undim) {
171     Undim_(self);
172     MSOldCall(undim);
173 }
174
175 static void Deactivate_(SBAwayController *self) {
176     [[cydgets_ objectAtIndex:active_] disableCydget:self];
177     active_ = 0;
178 }
179
180 MSInstanceMessageHook1(void, SBAwayController, dimScreen, BOOL, dim) {
181     Deactivate_([$SBAwayController sharedAwayController]);
182     MSOldCall(dim);
183 }
184
185 MSInstanceMessageHook1(void, SpringBoard, menuButtonDown, GSEventRef, event) {
186     menu_ = true;
187     MSOldCall(event);
188 }
189
190 MSInstanceMessageHook1(void, SBAwayView, addGestureRecognizer, id, recognizer) {
191     if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
192         return MSOldCall(recognizer);
193 }
194
195 MSInstanceMessageHook1(void, SBAwayWindow, sendGSEvent, GSEventRef, event) {
196     if (online_ == 0 || kCFCoreFoundationVersionNumber > 600)
197         return MSOldCall(event);
198
199     return MSSuperCall(event);
200 }
201
202 #define Cydgets_ @"/System/Library/LockCydgets"
203
204 static void CydgetSetup() {
205     NSString *plist([NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.Cydget.plist", NSHomeDirectory()]);
206     settings_ = [NSMutableDictionary dictionaryWithContentsOfFile:plist] ?: [NSMutableDictionary dictionary];
207
208     NSArray *cydgets([settings_ objectForKey:@"LockCydgets"] ?: [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:
209         @"Welcome", @"Name", [NSNumber numberWithBool:YES], @"Active", nil
210     ], [NSDictionary dictionaryWithObjectsAndKeys:
211         @"AwayView", @"Name", [NSNumber numberWithBool:YES], @"Active", nil
212     ], nil]);
213
214     cydgets_ = [NSMutableArray arrayWithCapacity:4];
215     for (NSDictionary *cydget in cydgets)
216         if ([[cydget objectForKey:@"Active"] boolValue])
217             if (NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/%@.cydget/Info.plist", Cydgets_, [cydget objectForKey:@"Name"]]])
218                 [cydgets_ addObject:info];
219
220     if ([cydgets_ count] == 0)
221         cydgets_ = nil;
222 }
223
224 // XXX: this could happen while it is unlocked
225 MSInstanceMessageHook1(id, SBAwayView, initWithFrame, CGRect, frame) {
226     self = MSOldCall(frame);
227     CydgetSetup();
228     [[cydgets_ objectAtIndex:active_] enableCydget:[$SBAwayController sharedAwayController]];
229     return self;
230 }
231
232 MSInstanceMessageHook1(void, SBAwayController, _finishedUnlockAttemptWithStatus, BOOL, status) {
233     if (status)
234         Deactivate_(self);
235     MSOldCall(status);
236 }
237
238 // this is called occasionally by -[SBAwayView updateInterface] and takes away our keyboard
239 MSInstanceMessageHook0(void, SBAwayView, _fixupFirstResponder) {
240     if (online_ == 0)
241         return MSOldCall();
242 }
243
244 MSInstanceMessageHook0(void, SBAwayView, updateInterface) {
245     MSOldCall();
246
247     NSDictionary *cydget([cydgets_ objectAtIndex:active_]);
248
249     NSString *background([cydget objectForKey:@"CYBackground"]);
250     if ([background isEqualToString:@"Wallpaper"]) {
251         MSIvarHook(UIView *, _backgroundView);
252         [_backgroundView setAlpha:1.0f];
253     }
254
255     if ([[cydget objectForKey:@"CYShowDateTime"] boolValue])
256         [self addDateView];
257 }