blob: a7dd3264dfe42b2e2dd2313ded15801e4e659049 [file] [log] [blame]
Alexandre Lision8521baa2015-03-13 11:08:00 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision8521baa2015-03-13 11:08:00 -04003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lision8521baa2015-03-13 11:08:00 -040018 */
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040019#import <SystemConfiguration/SystemConfiguration.h>
20
Alexandre Lision5855b6a2015-02-03 11:31:05 -050021#import "AppDelegate.h"
22
Alexandre Lisione4041492015-03-20 18:20:43 -040023#import <callmodel.h>
Edric Milaret81315412015-05-13 15:14:56 -040024#import <qapplication.h>
Alexandre Lision745e4d62015-03-22 20:03:10 -040025#import <accountmodel.h>
26#import <protocolmodel.h>
Alexandre Lision0f66bd32016-01-18 11:30:45 -050027#import <media/recordingmodel.h>
28#import <media/textrecording.h>
Alexandre Lision745e4d62015-03-22 20:03:10 -040029#import <QItemSelectionModel>
30#import <account.h>
Kateryna Kostiukabf4e272017-04-18 14:18:00 -040031#import <AvailableAccountModel.h>
32
Alexandre Lision745e4d62015-03-22 20:03:10 -040033
Alexandre Lision3d4143a2015-06-10 14:27:49 -040034#if ENABLE_SPARKLE
35#import <Sparkle/Sparkle.h>
36#endif
37
Alexandre Lisionc65310c2015-04-23 16:44:23 -040038#import "Constants.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040039#import "RingWizardWC.h"
Alexandre Lision62005312016-01-28 15:55:16 -050040#import "DialpadWC.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040041
Alexandre Lision3d4143a2015-06-10 14:27:49 -040042#if ENABLE_SPARKLE
43@interface AppDelegate() <SUUpdaterDelegate>
44#else
Alexandre Lision745e4d62015-03-22 20:03:10 -040045@interface AppDelegate()
Alexandre Lision3d4143a2015-06-10 14:27:49 -040046#endif
Alexandre Lision745e4d62015-03-22 20:03:10 -040047
48@property RingWindowController* ringWindowController;
49@property RingWizardWC* wizard;
Alexandre Lision62005312016-01-28 15:55:16 -050050@property DialpadWC* dialpad;
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040051@property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
52@property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
Alexandre Lision745e4d62015-03-22 20:03:10 -040053
54@end
55
Alexandre Lision5855b6a2015-02-03 11:31:05 -050056@implementation AppDelegate
57
Andreas Traczyke4d6e782018-03-22 17:51:30 -040058NSTimer* preventSleepTimer;
59
Alexandre Lision5855b6a2015-02-03 11:31:05 -050060- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Alexandre Lision4a7b95e2015-02-20 10:06:43 -050061 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
62
Alexandre Lisione4041492015-03-20 18:20:43 -040063 [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
64
Edric Milaret81315412015-05-13 15:14:56 -040065 NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
66 [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
67
Alexandre Lision745e4d62015-03-22 20:03:10 -040068 if([self checkForRingAccount]) {
69 [self showMainWindow];
70 } else {
71 [self showWizard];
72 }
Alexandre Lisione4041492015-03-20 18:20:43 -040073 [self connect];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040074
75 dispatch_queue_t queue = NULL;
76 queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
77 [self setScNetworkQueue:queue];
78 [self beginObservingReachabilityStatus];
Andreas Traczyke4d6e782018-03-22 17:51:30 -040079 [self startSleepPreventionTimer];
80}
81
82- (void) startSleepPreventionTimer
83{
84 if (preventSleepTimer != nil) {
85 [preventSleepTimer invalidate];
86 }
87 preventSleepTimer = [NSTimer timerWithTimeInterval:30.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
88 UpdateSystemActivity(OverallAct);
89 }];
90 [[NSRunLoop mainRunLoop] addTimer:preventSleepTimer forMode:NSRunLoopCommonModes];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040091}
92
93- (void) beginObservingReachabilityStatus
94{
95 SCNetworkReachabilityRef reachabilityRef = NULL;
96
97 void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
98 BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
99 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
100 AccountModel::instance().slotConnectivityChanged();
101 }];
102 };
103
104 SCNetworkReachabilityContext context = {
105 .version = 0,
106 .info = (void *)CFBridgingRetain(callbackBlock),
107 .release = CFRelease
108 };
109
110 reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "test");
111 if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
112 if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
113 // Remove our callback if we can't use the queue
114 SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
115 }
116 [self setCurrentReachability:reachabilityRef];
117 }
118}
119
120- (void) endObsvervingReachabilityStatusForHost:(NSString *)__unused host
121{
122 // Un-set the dispatch queue
123 if (SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
124 SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
125 }
126}
127
128static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkConnectionFlags flags, void* info)
129{
130 void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
131 callbackBlock(flags);
Alexandre Lisione4041492015-03-20 18:20:43 -0400132}
133
134- (void) connect
135{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400136 QObject::connect(&CallModel::instance(),
Alexandre Lisione4041492015-03-20 18:20:43 -0400137 &CallModel::incomingCall,
138 [=](Call* call) {
Kateryna Kostiukabf4e272017-04-18 14:18:00 -0400139 // on incoming call set selected account match call destination account
140 if (call->account()) {
141 QModelIndex index = call->account()->index();
142 index = AvailableAccountModel::instance().mapFromSource(index);
143
144 AvailableAccountModel::instance().selectionModel()->setCurrentIndex(index,
145 QItemSelectionModel::ClearAndSelect);
146 }
Alexandre Lisionc65310c2015-04-23 16:44:23 -0400147 BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
148 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400149 if (shouldComeToForeground) {
Alexandre Lisione4041492015-03-20 18:20:43 -0400150 [NSApp activateIgnoringOtherApps:YES];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400151 if ([self.ringWindowController.window isMiniaturized]) {
152 [self.ringWindowController.window deminiaturize:self];
153 }
154 }
Alexandre Lisione4041492015-03-20 18:20:43 -0400155
156 if(shouldNotify) {
157 [self showIncomingNotification:call];
158 }
159 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500160
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500161 QObject::connect(&Media::RecordingModel::instance(),
162 &Media::RecordingModel::newTextMessage,
163 [=](Media::TextRecording* t, ContactMethod* cm) {
164
165 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
166 auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);
167
168 // Don't show a notification if we are sending the text OR window already has focus OR user disabled notifications
169 if(qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction)) == Media::Media::Direction::OUT
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500170 || self.ringWindowController.window.isMainWindow || !shouldNotify)
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500171 return;
172
173 NSUserNotification* notification = [[NSUserNotification alloc] init];
174
Alexandre Lisionc8180112016-01-27 11:27:50 -0500175 NSString* localizedTitle = [NSString stringWithFormat:NSLocalizedString(@"Message from %@", @"Message from {Name}"), qIdx.data((int)Media::TextRecording::Role::AuthorDisplayname).toString().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500176
177 [notification setTitle:localizedTitle];
178 [notification setSoundName:NSUserNotificationDefaultSoundName];
179 [notification setSubtitle:qIdx.data().toString().toNSString()];
180
181 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
182 });
Alexandre Lisione4041492015-03-20 18:20:43 -0400183}
184
185- (void) showIncomingNotification:(Call*) call{
Alexandre Lision34607032016-02-08 16:16:49 -0500186 NSUserNotification* notification = [[NSUserNotification alloc] init];
Alexandre Lisionc8180112016-01-27 11:27:50 -0500187 NSString* localizedTitle = [NSString stringWithFormat:
188 NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500189 [notification setTitle:localizedTitle];
190 [notification setSoundName:NSUserNotificationDefaultSoundName];
Alexandre Lisione4041492015-03-20 18:20:43 -0400191
Alexandre Lision34607032016-02-08 16:16:49 -0500192 // try to activate action button
193 @try {
194 [notification setValue:@YES forKey:@"_showsButtons"];
195 }
196 @catch (NSException *exception) {
197 // private API _showsButtons has changed...
198 NSLog(@"Action button not activable on notification");
199 }
200 [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
201
Alexandre Lisione4041492015-03-20 18:20:43 -0400202 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500203}
204
Alexandre Lision34607032016-02-08 16:16:49 -0500205- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
206{
207 if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
208 CallModel::instance().selectedCall() << Call::Action::REFUSE;
209 } else {
210 [NSApp activateIgnoringOtherApps:YES];
211 if ([self.ringWindowController.window isMiniaturized]) {
212 [self.ringWindowController.window deminiaturize:self];
213 }
214 }
215}
216
Alexandre Lision745e4d62015-03-22 20:03:10 -0400217- (void) showWizard
218{
Alexandre Lision745e4d62015-03-22 20:03:10 -0400219 if(self.wizard == nil) {
220 self.wizard = [[RingWizardWC alloc] initWithWindowNibName:@"RingWizard"];
221 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500222 [self.wizard.window makeKeyAndOrderFront:self];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400223}
224
225- (void) showMainWindow
226{
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400227 if(self.ringWindowController == nil) {
Alexandre Lision745e4d62015-03-22 20:03:10 -0400228 self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow"];
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400229 }
Alexandre Lision745e4d62015-03-22 20:03:10 -0400230 [self.ringWindowController.window makeKeyAndOrderFront:self];
231}
232
Alexandre Lision62005312016-01-28 15:55:16 -0500233- (void) showDialpad
234{
235 if (self.dialpad == nil) {
236 self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
237 }
238 [self.dialpad.window makeKeyAndOrderFront:self];
239}
240
241
Alexandre Lision745e4d62015-03-22 20:03:10 -0400242- (BOOL) checkForRingAccount
243{
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400244 BOOL foundRingAcc = NO;
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400245 for (int i = 0 ; i < AccountModel::instance().rowCount() ; ++i) {
246 QModelIndex idx = AccountModel::instance().index(i);
247 Account* acc = AccountModel::instance().getAccountByModelIndex(idx);
Alexandre Lision76d59692016-01-20 18:06:05 -0500248 if(acc->protocol() == Account::Protocol::RING && !acc->isNew()) {
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400249 if (acc->displayName().isEmpty())
250 acc->setDisplayName(acc->alias());
251 foundRingAcc = YES;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400252 }
253 }
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400254 return foundRingAcc;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400255}
256
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400257-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
258{
259 NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
260 [appleEventManager setEventHandler:self
261 andSelector:@selector(handleGetURLEvent:withReplyEvent:)
262 forEventClass:kInternetEventClass andEventID:kAEGetURL];
263}
264
265/**
266 * Recognized patterns:
267 * - ring:<hash>
268 * - ring://<hash>
269 */
270- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
271{
272 NSString* query = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
273 NSURL* url = [[NSURL alloc] initWithString:query];
274 NSString* ringID = [url host];
275 if (!ringID) {
276 //not a valid NSURL, try to parse query directly
277 ringID = [query substringFromIndex:@"ring:".length];
278 }
279
280 // check for a valid ring hash
281 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
282 BOOL valid = [[ringID stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
283
284 if(valid && ringID.length == 40) {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400285 Call* c = CallModel::instance().dialingCall();
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400286 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",ringID]));
287 c << Call::Action::ACCEPT;
288 } else {
289 NSAlert *alert = [[NSAlert alloc] init];
290 [alert addButtonWithTitle:@"OK"];
291 [alert setMessageText:@"Error"];
292 [alert setInformativeText:@"ringID cannot be read from this URL."];
293 [alert setAlertStyle:NSWarningAlertStyle];
294 [alert runModal];
295 }
296}
297
Alexandre Lision745e4d62015-03-22 20:03:10 -0400298- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
299{
300 if([self checkForRingAccount]) {
301 [self showMainWindow];
302 } else {
303 [self showWizard];
304 }
305 return YES;
306}
307
Edric Milaret81315412015-05-13 15:14:56 -0400308- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
309{
Alexandre Lision76d59692016-01-20 18:06:05 -0500310 [self cleanExit];
Edric Milaret81315412015-05-13 15:14:56 -0400311}
312
313-(void)applicationWillTerminate:(NSNotification *)notification
314{
Alexandre Lision76d59692016-01-20 18:06:05 -0500315 [self cleanExit];
316}
317
318- (void) cleanExit
319{
Andreas Traczyke4d6e782018-03-22 17:51:30 -0400320 if (preventSleepTimer != nil) {
321 [preventSleepTimer invalidate];
322 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500323 [self.wizard close];
324 [self.ringWindowController close];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400325 delete CallModel::instance().QObject::parent();
Edric Milaret81315412015-05-13 15:14:56 -0400326 [[NSApplication sharedApplication] terminate:self];
327}
328
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400329#if ENABLE_SPARKLE
330
Alexandre Lision76d59692016-01-20 18:06:05 -0500331#pragma mark - Sparkle delegate
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400332
333- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
334{
335 [NSApp activateIgnoringOtherApps:YES];
336}
337
338- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
339{
340 return YES;
341}
342
343- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater
344{
345 return YES;
346}
347
348- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error
349{
350 NSLog(@"Error:%@", error.localizedDescription);
351}
352
353#endif
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500354@end