blob: 534c61b01d72caa82fd0504e574e97ec8b1e8083 [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>
Andreas Traczykf1d21902018-04-09 11:01:49 -040030#import <QDebug>
Alexandre Lision745e4d62015-03-22 20:03:10 -040031#import <account.h>
Kateryna Kostiukabf4e272017-04-18 14:18:00 -040032#import <AvailableAccountModel.h>
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040033#import <api/lrc.h>
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -050034#import <api/newaccountmodel.h>
Kateryna Kostiukabf4e272017-04-18 14:18:00 -040035
Alexandre Lision745e4d62015-03-22 20:03:10 -040036
Alexandre Lision3d4143a2015-06-10 14:27:49 -040037#if ENABLE_SPARKLE
38#import <Sparkle/Sparkle.h>
39#endif
40
Alexandre Lisionc65310c2015-04-23 16:44:23 -040041#import "Constants.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040042#import "RingWizardWC.h"
Alexandre Lision62005312016-01-28 15:55:16 -050043#import "DialpadWC.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040044
Alexandre Lision3d4143a2015-06-10 14:27:49 -040045#if ENABLE_SPARKLE
46@interface AppDelegate() <SUUpdaterDelegate>
47#else
Alexandre Lision745e4d62015-03-22 20:03:10 -040048@interface AppDelegate()
Alexandre Lision3d4143a2015-06-10 14:27:49 -040049#endif
Alexandre Lision745e4d62015-03-22 20:03:10 -040050
51@property RingWindowController* ringWindowController;
52@property RingWizardWC* wizard;
Alexandre Lision62005312016-01-28 15:55:16 -050053@property DialpadWC* dialpad;
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040054@property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
55@property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
Adrien Béraud022b57a2018-09-15 15:49:07 -040056@property (strong) id activity;
Alexandre Lision745e4d62015-03-22 20:03:10 -040057
58@end
59
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040060@implementation AppDelegate {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050061
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040062std::unique_ptr<lrc::api::Lrc> lrc;
63}
Andreas Traczyke4d6e782018-03-22 17:51:30 -040064
Alexandre Lision5855b6a2015-02-03 11:31:05 -050065- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Kateryna Kostiuk0bc4c592018-06-05 13:35:19 -040066
67 // hide "Check for update" menu item when sparkle is disabled
68 NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
69 NSMenu *ringMenu = [[mainMenu itemAtIndex:0] submenu];
70 NSMenuItem *updateItem = [ringMenu itemAtIndex:1];
71#if ENABLE_SPARKLE
72 updateItem.hidden = false;
73#else
74 updateItem.hidden = true;
75#endif
76
Alexandre Lision4a7b95e2015-02-20 10:06:43 -050077 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
78
Alexandre Lisione4041492015-03-20 18:20:43 -040079 [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
80
Edric Milaret81315412015-05-13 15:14:56 -040081 NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
82 [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040083 lrc = std::make_unique<lrc::api::Lrc>();
Edric Milaret81315412015-05-13 15:14:56 -040084
Alexandre Lision745e4d62015-03-22 20:03:10 -040085 if([self checkForRingAccount]) {
86 [self showMainWindow];
87 } else {
88 [self showWizard];
89 }
Alexandre Lisione4041492015-03-20 18:20:43 -040090 [self connect];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040091
92 dispatch_queue_t queue = NULL;
93 queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
94 [self setScNetworkQueue:queue];
95 [self beginObservingReachabilityStatus];
Adrien Béraud022b57a2018-09-15 15:49:07 -040096 NSActivityOptions options = NSActivitySuddenTerminationDisabled | NSActivityAutomaticTerminationDisabled | NSActivityBackground;
97 self.activity = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:@"Receiving calls and messages"];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040098}
99
100- (void) beginObservingReachabilityStatus
101{
102 SCNetworkReachabilityRef reachabilityRef = NULL;
103
104 void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
105 BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
106 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
107 AccountModel::instance().slotConnectivityChanged();
108 }];
109 };
110
111 SCNetworkReachabilityContext context = {
112 .version = 0,
113 .info = (void *)CFBridgingRetain(callbackBlock),
114 .release = CFRelease
115 };
116
117 reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "test");
118 if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
119 if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
120 // Remove our callback if we can't use the queue
121 SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
122 }
123 [self setCurrentReachability:reachabilityRef];
124 }
125}
126
127- (void) endObsvervingReachabilityStatusForHost:(NSString *)__unused host
128{
129 // Un-set the dispatch queue
130 if (SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
131 SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
132 }
133}
134
135static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkConnectionFlags flags, void* info)
136{
137 void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
138 callbackBlock(flags);
Alexandre Lisione4041492015-03-20 18:20:43 -0400139}
140
141- (void) connect
142{
Andreas Traczykf1d21902018-04-09 11:01:49 -0400143
144 //ProfileModel::instance().addCollection<LocalProfileCollection>(LoadOptions::FORCE_ENABLED);
145 QObject::connect(&AccountModel::instance(),
146 &AccountModel::registrationChanged,
147 [=](Account* a, bool registration) {
148 qDebug() << "registrationChanged:" << a->id() << ":" << registration;
149 //track buddy for account
150 AccountModel::instance().subscribeToBuddies(a->id());
151 });
152
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400153 QObject::connect(&CallModel::instance(),
Alexandre Lisione4041492015-03-20 18:20:43 -0400154 &CallModel::incomingCall,
155 [=](Call* call) {
Kateryna Kostiukabf4e272017-04-18 14:18:00 -0400156 // on incoming call set selected account match call destination account
157 if (call->account()) {
158 QModelIndex index = call->account()->index();
159 index = AvailableAccountModel::instance().mapFromSource(index);
160
161 AvailableAccountModel::instance().selectionModel()->setCurrentIndex(index,
162 QItemSelectionModel::ClearAndSelect);
163 }
Alexandre Lisionc65310c2015-04-23 16:44:23 -0400164 BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
165 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400166 if (shouldComeToForeground) {
Alexandre Lisione4041492015-03-20 18:20:43 -0400167 [NSApp activateIgnoringOtherApps:YES];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400168 if ([self.ringWindowController.window isMiniaturized]) {
169 [self.ringWindowController.window deminiaturize:self];
170 }
171 }
Alexandre Lisione4041492015-03-20 18:20:43 -0400172
173 if(shouldNotify) {
174 [self showIncomingNotification:call];
175 }
176 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500177
Kateryna Kostiukf9a72c02018-08-01 14:28:49 -0400178 QObject::connect(&media::RecordingModel::instance(),
179 &media::RecordingModel::newTextMessage,
180 [=](media::TextRecording* t, ContactMethod* cm) {
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500181
182 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
183 auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);
184
185 // Don't show a notification if we are sending the text OR window already has focus OR user disabled notifications
Kateryna Kostiukf9a72c02018-08-01 14:28:49 -0400186 if(qvariant_cast<media::Media::Direction>(qIdx.data((int)media::TextRecording::Role::Direction)) == media::Media::Direction::OUT
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500187 || self.ringWindowController.window.isMainWindow || !shouldNotify)
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500188 return;
189
190 NSUserNotification* notification = [[NSUserNotification alloc] init];
191
Kateryna Kostiukf9a72c02018-08-01 14:28:49 -0400192 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 -0500193
194 [notification setTitle:localizedTitle];
195 [notification setSoundName:NSUserNotificationDefaultSoundName];
196 [notification setSubtitle:qIdx.data().toString().toNSString()];
197
198 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
199 });
Alexandre Lisione4041492015-03-20 18:20:43 -0400200}
201
202- (void) showIncomingNotification:(Call*) call{
Alexandre Lision34607032016-02-08 16:16:49 -0500203 NSUserNotification* notification = [[NSUserNotification alloc] init];
Alexandre Lisionc8180112016-01-27 11:27:50 -0500204 NSString* localizedTitle = [NSString stringWithFormat:
205 NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500206 [notification setTitle:localizedTitle];
207 [notification setSoundName:NSUserNotificationDefaultSoundName];
Alexandre Lisione4041492015-03-20 18:20:43 -0400208
Alexandre Lision34607032016-02-08 16:16:49 -0500209 // try to activate action button
210 @try {
211 [notification setValue:@YES forKey:@"_showsButtons"];
212 }
213 @catch (NSException *exception) {
214 // private API _showsButtons has changed...
215 NSLog(@"Action button not activable on notification");
216 }
217 [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
218
Alexandre Lisione4041492015-03-20 18:20:43 -0400219 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500220}
221
Alexandre Lision34607032016-02-08 16:16:49 -0500222- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
223{
224 if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
225 CallModel::instance().selectedCall() << Call::Action::REFUSE;
226 } else {
227 [NSApp activateIgnoringOtherApps:YES];
228 if ([self.ringWindowController.window isMiniaturized]) {
229 [self.ringWindowController.window deminiaturize:self];
230 }
231 }
232}
233
Alexandre Lision745e4d62015-03-22 20:03:10 -0400234- (void) showWizard
235{
Alexandre Lision745e4d62015-03-22 20:03:10 -0400236 if(self.wizard == nil) {
Kateryna Kostiukecaa3952018-07-13 16:00:34 -0400237 self.wizard = [[RingWizardWC alloc] initWithNibName:@"RingWizard" bundle: nil accountmodel: &lrc->getAccountModel()];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400238 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500239 [self.wizard.window makeKeyAndOrderFront:self];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400240}
241
242- (void) showMainWindow
243{
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400244 if(self.ringWindowController == nil) {
Kateryna Kostiukecaa3952018-07-13 16:00:34 -0400245 self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow" bundle: nil accountModel:&lrc->getAccountModel() dataTransferModel:&lrc->getDataTransferModel() behaviourController:&lrc->getBehaviorController()];
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400246 }
Kateryna Kostiuk465cfbe2018-06-05 16:13:05 -0400247 [[NSApplication sharedApplication] removeWindowsItem:self.wizard.window];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400248 [self.ringWindowController.window makeKeyAndOrderFront:self];
249}
250
Alexandre Lision62005312016-01-28 15:55:16 -0500251- (void) showDialpad
252{
253 if (self.dialpad == nil) {
254 self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
255 }
256 [self.dialpad.window makeKeyAndOrderFront:self];
257}
258
259
Alexandre Lision745e4d62015-03-22 20:03:10 -0400260- (BOOL) checkForRingAccount
261{
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -0500262 return !lrc->getAccountModel().getAccountList().empty();
Alexandre Lision745e4d62015-03-22 20:03:10 -0400263}
264
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400265-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
266{
267 NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
268 [appleEventManager setEventHandler:self
269 andSelector:@selector(handleGetURLEvent:withReplyEvent:)
270 forEventClass:kInternetEventClass andEventID:kAEGetURL];
271}
272
273/**
274 * Recognized patterns:
275 * - ring:<hash>
276 * - ring://<hash>
277 */
278- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
279{
280 NSString* query = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
281 NSURL* url = [[NSURL alloc] initWithString:query];
282 NSString* ringID = [url host];
283 if (!ringID) {
284 //not a valid NSURL, try to parse query directly
285 ringID = [query substringFromIndex:@"ring:".length];
286 }
287
288 // check for a valid ring hash
289 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
290 BOOL valid = [[ringID stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
291
292 if(valid && ringID.length == 40) {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400293 Call* c = CallModel::instance().dialingCall();
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400294 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",ringID]));
295 c << Call::Action::ACCEPT;
296 } else {
297 NSAlert *alert = [[NSAlert alloc] init];
298 [alert addButtonWithTitle:@"OK"];
299 [alert setMessageText:@"Error"];
300 [alert setInformativeText:@"ringID cannot be read from this URL."];
301 [alert setAlertStyle:NSWarningAlertStyle];
302 [alert runModal];
303 }
304}
305
Alexandre Lision745e4d62015-03-22 20:03:10 -0400306- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
307{
308 if([self checkForRingAccount]) {
309 [self showMainWindow];
310 } else {
311 [self showWizard];
312 }
313 return YES;
314}
315
Edric Milaret81315412015-05-13 15:14:56 -0400316- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
317{
Kateryna Kostiuk0c0344a2018-07-27 10:35:01 -0400318 [[NSApplication sharedApplication] terminate:self];
Edric Milaret81315412015-05-13 15:14:56 -0400319}
320
321-(void)applicationWillTerminate:(NSNotification *)notification
322{
Alexandre Lision76d59692016-01-20 18:06:05 -0500323 [self cleanExit];
324}
325
326- (void) cleanExit
327{
Adrien Béraud022b57a2018-09-15 15:49:07 -0400328 if (self.activity != nil) {
329 [[NSProcessInfo processInfo] endActivity:self.activity];
330 self.activity = nil;
Andreas Traczyke4d6e782018-03-22 17:51:30 -0400331 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500332 [self.wizard close];
333 [self.ringWindowController close];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400334 delete CallModel::instance().QObject::parent();
Edric Milaret81315412015-05-13 15:14:56 -0400335 [[NSApplication sharedApplication] terminate:self];
336}
337
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400338#if ENABLE_SPARKLE
339
Alexandre Lision76d59692016-01-20 18:06:05 -0500340#pragma mark - Sparkle delegate
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400341
342- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
343{
344 [NSApp activateIgnoringOtherApps:YES];
345}
346
347- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
348{
349 return YES;
350}
351
352- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater
353{
354 return YES;
355}
356
357- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error
358{
359 NSLog(@"Error:%@", error.localizedDescription);
360}
361
362#endif
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500363@end