2 * Copyright (C) 2021 Cameron Katri
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 #import <Foundation/NSUserDefaults.h>
18 #import <CoreGraphics/CoreGraphics.h>
19 #import <UIKit/UIKit.h>
23 void openApplication(NSString *bundleID)
25 FBSOpenApplicationOptions* opts = [%c(FBSOpenApplicationOptions) optionsWithDictionary:@{
26 @"__LaunchOrigin" : @"BulletinDestinationCoverSheet",
27 @"__PromptUnlockDevice" : @YES,
28 @"__UnlockDevice" : @YES,
29 @"__LaunchImage" : @"",
32 FBSystemServiceOpenApplicationRequest* request = [%c(FBSystemServiceOpenApplicationRequest) request];
33 request.options = opts;
34 request.bundleIdentifier = bundleID;
35 request.trusted = YES;
36 request.clientProcess = [[%c(FBProcessManager) sharedInstance] systemApplicationProcess];
38 [[%c(SBMainWorkspace) sharedInstance] systemService:[%c(FBSystemService) sharedInstance] handleOpenApplicationRequest:request withCompletion:^{}];
41 %hook CSQuickActionsView
43 %property (nonatomic, retain) NSMutableArray * leftButtons;
44 %property (nonatomic, retain) NSMutableArray * rightButtons;
45 %property (nonatomic) BOOL leftOpen;
46 %property (nonatomic) BOOL rightOpen;
47 %property (nonatomic) BOOL collapseLeft;
48 %property (nonatomic) BOOL collapseRight;
49 %property (nonatomic, retain) DNDStateService *stateService;
51 -(id)initWithFrame:(CGRect)arg1 delegate:(id)arg2
55 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"];
57 NSArray *leftButtons = [defaults arrayForKey:@"leftButtons"];
58 if (leftButtons == nil)
59 leftButtons = @[@"com.apple.flashlight"];
60 NSArray *rightButtons = [defaults arrayForKey:@"rightButtons"];
61 if (rightButtons == nil)
62 rightButtons = @[@"com.apple.camera"];
64 self.leftButtons = [[NSMutableArray alloc] init];
65 self.rightButtons = [[NSMutableArray alloc] init];
67 self.collapseLeft = [leftButtons count] > 1 ? true : false;
68 self.leftOpen = !self.collapseLeft;
70 if ([leftButtons count] == 0) {
71 [self.flashlightButton setHidden:1];
72 } else if ([leftButtons count] == 1) {
73 [self.flashlightButton setBundleID:leftButtons[0]];
74 } else if ([leftButtons count] > 1) {
75 [self.flashlightButton setImage:nil];
76 for (int i = 0; i < [leftButtons count]; i++) {
77 CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)];
78 [button setBundleID:leftButtons[i]];
80 [button setBackgroundEffectViewGroupName:[self _buttonGroupName]];
81 [button setLegibilitySettings:[self legibilitySettings]];
82 [button setPermitted:1];
84 [self insertSubview:button belowSubview:self.flashlightButton];
85 [self _addTargetsToButton:button];
86 [self.leftButtons addObject:button];
90 self.collapseRight = [rightButtons count] > 1 ? true : false;
91 self.rightOpen = !self.collapseRight;
93 if ([rightButtons count] == 0) {
94 [self.cameraButton setHidden:1];
95 } else if ([rightButtons count] == 1) {
96 [self.cameraButton setBundleID:rightButtons[0]];
97 } else if ([rightButtons count] > 1) {
98 [self.cameraButton setImage:nil];
99 for (int i = 0; i < [rightButtons count]; i++) {
100 CSQuickActionsButton *button = [[CSQuickActionsButton alloc] initWithType:(i + 2)];
101 [button setBundleID:rightButtons[i]];
103 [button setBackgroundEffectViewGroupName:[self _buttonGroupName]];
104 [button setLegibilitySettings:[self legibilitySettings]];
105 [button setPermitted:1];
107 [self insertSubview:button belowSubview:self.cameraButton];
108 [self _addTargetsToButton:button];
109 [self.rightButtons addObject:button];
113 self.stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
114 [self.stateService addStateUpdateListener:self withCompletionHandler:nil];
119 -(void)refreshFlashlightAvailability
124 self.leftOpen = !self.leftOpen;
125 for (CSQuickActionsButton *button in [self leftButtons]) {
126 button.frame = [self leftFrameForButton:button];
127 [button setHidden:!self.leftOpen];
130 if (self.rightOpen) {
131 self.rightOpen = !self.rightOpen;
132 for (CSQuickActionsButton *button in [self rightButtons]) {
133 button.frame = [self rightFrameForButton:button];
134 [button setHidden:!self.rightOpen];
140 -(CGRect)rightFrameForButton:(CSQuickActionsButton*)button
142 CGRect cameraFrame = [[self cameraButton] frame];
143 if (self.rightOpen) {
144 return CGRectMake(cameraFrame.origin.x,
145 cameraFrame.origin.y - ((cameraFrame.size.height * 3/4) * (button.type - 1)),
146 cameraFrame.size.width, cameraFrame.size.height);
153 -(CGRect)leftFrameForButton:(CSQuickActionsButton*)button
155 CGRect flashlightFrame = [[self flashlightButton] frame];
157 return CGRectMake(flashlightFrame.origin.x,
158 flashlightFrame.origin.y - ((flashlightFrame.size.height * 3/4) * (button.type - 1)),
159 flashlightFrame.size.width, flashlightFrame.size.height);
161 return flashlightFrame;
165 -(void)setLegibilitySettings:(id)legibilitySettings
168 for (CSQuickActionsButton *button in [self leftButtons])
169 [button setLegibilitySettings:legibilitySettings];
170 for (CSQuickActionsButton *button in [self rightButtons])
171 [button setLegibilitySettings:legibilitySettings];
174 -(void)_layoutQuickActionButtons
178 UIEdgeInsets insets = [self _buttonOutsets];
179 if (SBFEffectiveHomeButtonType() != 2) {
180 CGRect bounds = [[UIScreen mainScreen] _referenceBounds];
182 // Detect if we are on an iPhone SE and adjust the insets
183 // accordingly to not overlap with the text
184 if ([[UIScreen mainScreen] nativeBounds].size.height == 1136)
187 CGFloat buttonWidth = 50 + insets.right + insets.left;
188 CGFloat buttonHeight = 50 + insets.top + insets.bottom;
190 [[self flashlightButton] setEdgeInsets:insets];
192 self.flashlightButton.frame = CGRectMake(insets.left,
193 bounds.size.height - buttonHeight - insets.bottom,
194 buttonWidth, buttonHeight);
196 [[self cameraButton] setEdgeInsets:insets];
198 self.cameraButton.frame = CGRectMake(bounds.size.width - insets.left - buttonWidth,
199 bounds.size.height - buttonHeight - insets.bottom,
200 buttonWidth, buttonHeight);
203 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.cameronkatri.quickactions"];
205 CGRect frame = self.flashlightButton.frame;
206 frame.origin.x += [defaults floatForKey:@"LeftOffsetX"];
207 frame.origin.y -= [defaults floatForKey:@"LeftOffsetY"];
208 self.flashlightButton.frame = frame;
210 frame = self.cameraButton.frame;
211 frame.origin.x -= [defaults floatForKey:@"RightOffsetX"];
212 frame.origin.y -= [defaults floatForKey:@"RightOffsetY"];
213 self.cameraButton.frame = frame;
215 UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle1];
216 UIImage *image = [UIImage systemImageNamed:@"ellipsis" withConfiguration:imageConfig];
217 if ([self.leftButtons count] > 1) {
218 [self.flashlightButton setImage:image];
219 ((UIImageView*)[self.flashlightButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
221 if ([self.rightButtons count] > 1) {
222 [self.cameraButton setImage:image];
223 ((UIImageView*)[self.cameraButton valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
226 for (CSQuickActionsButton *button in [self leftButtons]) {
227 [button setEdgeInsets:insets];
228 button.frame = [self leftFrameForButton:button];
229 [button setHidden:!self.leftOpen];
231 for (CSQuickActionsButton *button in [self rightButtons]) {
232 [button setEdgeInsets:insets];
233 button.frame = [self rightFrameForButton:button];
234 [button setHidden:!self.rightOpen];
237 [self updateDND:nil];
240 -(void)handleButtonPress:(CSQuickActionsButton *)button
242 [button setSelected:false];
244 if (button.type == 0 && self.collapseRight) {
245 self.rightOpen = !self.rightOpen;
246 [UIView animateWithDuration:0.25
248 options:UIViewAnimationOptionCurveEaseOut
250 for (CSQuickActionsButton *button in [self rightButtons]) {
251 button.frame = [self rightFrameForButton:button];
253 [button setHidden:0];
256 completion:^(BOOL finished) {
257 for (CSQuickActionsButton *button in [self rightButtons])
258 [button setHidden:!self.rightOpen];
260 } else if (button.type == 1 && self.collapseLeft) {
261 self.leftOpen = !self.leftOpen;
262 [UIView animateWithDuration:0.25
264 options:UIViewAnimationOptionCurveEaseOut
266 for (CSQuickActionsButton *button in [self leftButtons]) {
267 button.frame = [self leftFrameForButton:button];
269 [button setHidden:0];
272 completion:^(BOOL finished) {
273 for (CSQuickActionsButton *button in [self leftButtons])
274 [button setHidden:!self.leftOpen];
276 } else if ([button.bundleID isEqualToString:@"com.apple.flashlight"]) {
277 [self.delegate _toggleFlashlight];
278 } else if ([button.bundleID isEqualToString:@"com.apple.camera"]) {
279 [self.delegate _launchCamera];
280 } else if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"]) {
281 [self setDoNotDisturb:!self.isDNDActive];
282 } else if (button.bundleID)
283 openApplication(button.bundleID);
289 /* This will make the CC module be correct */
290 [self.delegate _resetIdleTimer];
291 [self.delegate sendAction:[CSAction actionWithType:5]];
296 -(void)handleButtonTouchBegan:(CSQuickActionsButton *)button
298 if (button.bundleID != nil ||
299 (button.type == 0 && self.collapseRight) ||
300 (button.type == 1 && self.collapseLeft))
305 -(void)handleButtonTouchEnded:(CSQuickActionsButton *)button
307 if (button.bundleID != nil ||
308 (button.type == 0 && self.collapseRight) ||
309 (button.type == 1 && self.collapseLeft))
314 -(void)setFlashlightOn:(BOOL)arg
316 if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.flashlight"])
317 [self.flashlightButton setSelected:arg];
318 if ([self.cameraButton.bundleID isEqualToString:@"com.apple.flashlight"])
319 [self.cameraButton setSelected:arg];
320 for (CSQuickActionsButton *button in [self leftButtons])
321 if ([button.bundleID isEqualToString:@"com.apple.flashlight"])
322 [button setSelected:arg];
323 for (CSQuickActionsButton *button in [self rightButtons])
324 if ([button.bundleID isEqualToString:@"com.apple.flashlight"])
325 [button setSelected:arg];
329 -(void)setDoNotDisturb:(BOOL)state
331 DNDModeAssertionService *assertionService = (DNDModeAssertionService *)[objc_getClass("DNDModeAssertionService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
334 DNDModeAssertionDetails *newAssertion = [objc_getClass("DNDModeAssertionDetails") userRequestedAssertionDetailsWithIdentifier:@"com.apple.control-center.manual-toggle" modeIdentifier:@"com.apple.donotdisturb.mode.default" lifetime:nil];
335 [assertionService takeModeAssertionWithDetails:newAssertion error:NULL];
337 [assertionService invalidateAllActiveModeAssertionsWithError:NULL];
340 [[NSNotificationCenter defaultCenter] postNotificationName:@"SBQuietModeStatusChangedNotification" object:nil];
347 // DNDStateService *stateService = (DNDStateService *)[objc_getClass("DNDStateService") serviceForClientIdentifier:@"com.apple.donotdisturb.control-center.module"];
348 return [[self.stateService queryCurrentStateWithError:nil] isActive];
352 -(void)updateDND:(NSNotification *)notif
354 BOOL active = [self isDNDActive];
356 if ([self.flashlightButton.bundleID isEqualToString:@"com.apple.donotdisturb"])
357 [self.flashlightButton setSelected:active];
358 if ([self.cameraButton.bundleID isEqualToString:@"com.apple.donotdisturb"])
359 [self.cameraButton setSelected:active];
360 for (CSQuickActionsButton *button in [self leftButtons])
361 if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"])
362 [button setSelected:active];
363 for (CSQuickActionsButton *button in [self rightButtons])
364 if ([button.bundleID isEqualToString:@"com.apple.donotdisturb"])
365 [button setSelected:active];
369 -(void)stateService:(id)arg1 didReceiveDoNotDisturbStateUpdate:(id)arg2
371 dispatch_async(dispatch_get_main_queue(), ^{
372 [self updateDND:nil];
378 %hook CSQuickActionsButton
380 %property (nonatomic, retain) NSString *bundleID;
382 -(void)setImage:(UIImage *)img
385 [[self valueForKey:@"_contentView"] setImage:img];
388 -(void)setBundleID:(NSString*)bundleID
391 if ([bundleID isEqualToString:@"com.apple.camera"]) {
392 [self setImage:[self _imageWithName:@"OrbCamera"]];
393 [self setLatching:FALSE];
394 } else if ([bundleID isEqualToString:@"com.apple.flashlight"]) {
395 [self setImage:[self _imageWithName:@"OrbFlashlightOff"]];
396 [self setSelectedImage:[self _imageWithName:@"OrbFlashlightOff"]];
397 [self setLatching:TRUE];
398 } else if (([bundleID isEqualToString:@"com.apple.donotdisturb"])) {
399 UIImageSymbolConfiguration *imageConfig = [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleTitle2];
400 [self setImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]];
401 [self setSelectedImage:[UIImage systemImageNamed:@"moon.fill" withConfiguration:imageConfig]];
402 ((UIImageView *)[self valueForKey:@"_contentView"]).contentMode = UIViewContentModeScaleAspectFit;
403 [self setLatching:TRUE];
405 [self setImage:[UIImage _applicationIconImageForBundleIdentifier:bundleID format:0 scale:[UIScreen mainScreen].scale]];
406 [self setLatching:FALSE];
410 -(void)setPermitted:(BOOL)permitted
417 %hook CSQuickActionsViewController
429 +(BOOL)deviceSupportsButtons
436 // vim: filetype=logos