blob: 3d7953738cbe5a3f0fc45e5d65b46eb9b2f2b90f [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>
33
Alexandre Lision745e4d62015-03-22 20:03:10 -040034
Alexandre Lision3d4143a2015-06-10 14:27:49 -040035#if ENABLE_SPARKLE
36#import <Sparkle/Sparkle.h>
37#endif
38
Alexandre Lisionc65310c2015-04-23 16:44:23 -040039#import "Constants.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040040#import "RingWizardWC.h"
Alexandre Lision62005312016-01-28 15:55:16 -050041#import "DialpadWC.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040042
Alexandre Lision3d4143a2015-06-10 14:27:49 -040043#if ENABLE_SPARKLE
44@interface AppDelegate() <SUUpdaterDelegate>
45#else
Alexandre Lision745e4d62015-03-22 20:03:10 -040046@interface AppDelegate()
Alexandre Lision3d4143a2015-06-10 14:27:49 -040047#endif
Alexandre Lision745e4d62015-03-22 20:03:10 -040048
49@property RingWindowController* ringWindowController;
50@property RingWizardWC* wizard;
Alexandre Lision62005312016-01-28 15:55:16 -050051@property DialpadWC* dialpad;
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040052@property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
53@property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
Alexandre Lision745e4d62015-03-22 20:03:10 -040054
55@end
56
Alexandre Lision5855b6a2015-02-03 11:31:05 -050057@implementation AppDelegate
58
Andreas Traczyke4d6e782018-03-22 17:51:30 -040059NSTimer* preventSleepTimer;
60
Alexandre Lision5855b6a2015-02-03 11:31:05 -050061- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Kateryna Kostiuk0bc4c592018-06-05 13:35:19 -040062
63 // hide "Check for update" menu item when sparkle is disabled
64 NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
65 NSMenu *ringMenu = [[mainMenu itemAtIndex:0] submenu];
66 NSMenuItem *updateItem = [ringMenu itemAtIndex:1];
67#if ENABLE_SPARKLE
68 updateItem.hidden = false;
69#else
70 updateItem.hidden = true;
71#endif
72
Alexandre Lision4a7b95e2015-02-20 10:06:43 -050073 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
74
Alexandre Lisione4041492015-03-20 18:20:43 -040075 [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
76
Edric Milaret81315412015-05-13 15:14:56 -040077 NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
78 [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
79
Alexandre Lision745e4d62015-03-22 20:03:10 -040080 if([self checkForRingAccount]) {
81 [self showMainWindow];
82 } else {
83 [self showWizard];
84 }
Alexandre Lisione4041492015-03-20 18:20:43 -040085 [self connect];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040086
87 dispatch_queue_t queue = NULL;
88 queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
89 [self setScNetworkQueue:queue];
90 [self beginObservingReachabilityStatus];
Andreas Traczyke4d6e782018-03-22 17:51:30 -040091 [self startSleepPreventionTimer];
92}
93
94- (void) startSleepPreventionTimer
95{
96 if (preventSleepTimer != nil) {
97 [preventSleepTimer invalidate];
98 }
99 preventSleepTimer = [NSTimer timerWithTimeInterval:30.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
100 UpdateSystemActivity(OverallAct);
101 }];
102 [[NSRunLoop mainRunLoop] addTimer:preventSleepTimer forMode:NSRunLoopCommonModes];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -0400103}
104
105- (void) beginObservingReachabilityStatus
106{
107 SCNetworkReachabilityRef reachabilityRef = NULL;
108
109 void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
110 BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
111 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
112 AccountModel::instance().slotConnectivityChanged();
113 }];
114 };
115
116 SCNetworkReachabilityContext context = {
117 .version = 0,
118 .info = (void *)CFBridgingRetain(callbackBlock),
119 .release = CFRelease
120 };
121
122 reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "test");
123 if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
124 if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
125 // Remove our callback if we can't use the queue
126 SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
127 }
128 [self setCurrentReachability:reachabilityRef];
129 }
130}
131
132- (void) endObsvervingReachabilityStatusForHost:(NSString *)__unused host
133{
134 // Un-set the dispatch queue
135 if (SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
136 SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
137 }
138}
139
140static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkConnectionFlags flags, void* info)
141{
142 void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
143 callbackBlock(flags);
Alexandre Lisione4041492015-03-20 18:20:43 -0400144}
145
146- (void) connect
147{
Andreas Traczykf1d21902018-04-09 11:01:49 -0400148
149 //ProfileModel::instance().addCollection<LocalProfileCollection>(LoadOptions::FORCE_ENABLED);
150 QObject::connect(&AccountModel::instance(),
151 &AccountModel::registrationChanged,
152 [=](Account* a, bool registration) {
153 qDebug() << "registrationChanged:" << a->id() << ":" << registration;
154 //track buddy for account
155 AccountModel::instance().subscribeToBuddies(a->id());
156 });
157
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400158 QObject::connect(&CallModel::instance(),
Alexandre Lisione4041492015-03-20 18:20:43 -0400159 &CallModel::incomingCall,
160 [=](Call* call) {
Kateryna Kostiukabf4e272017-04-18 14:18:00 -0400161 // on incoming call set selected account match call destination account
162 if (call->account()) {
163 QModelIndex index = call->account()->index();
164 index = AvailableAccountModel::instance().mapFromSource(index);
165
166 AvailableAccountModel::instance().selectionModel()->setCurrentIndex(index,
167 QItemSelectionModel::ClearAndSelect);
168 }
Alexandre Lisionc65310c2015-04-23 16:44:23 -0400169 BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
170 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400171 if (shouldComeToForeground) {
Alexandre Lisione4041492015-03-20 18:20:43 -0400172 [NSApp activateIgnoringOtherApps:YES];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400173 if ([self.ringWindowController.window isMiniaturized]) {
174 [self.ringWindowController.window deminiaturize:self];
175 }
176 }
Alexandre Lisione4041492015-03-20 18:20:43 -0400177
178 if(shouldNotify) {
179 [self showIncomingNotification:call];
180 }
181 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500182
Kateryna Kostiukf9a72c02018-08-01 14:28:49 -0400183 QObject::connect(&media::RecordingModel::instance(),
184 &media::RecordingModel::newTextMessage,
185 [=](media::TextRecording* t, ContactMethod* cm) {
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500186
187 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
188 auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);
189
190 // 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 -0400191 if(qvariant_cast<media::Media::Direction>(qIdx.data((int)media::TextRecording::Role::Direction)) == media::Media::Direction::OUT
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500192 || self.ringWindowController.window.isMainWindow || !shouldNotify)
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500193 return;
194
195 NSUserNotification* notification = [[NSUserNotification alloc] init];
196
Kateryna Kostiukf9a72c02018-08-01 14:28:49 -0400197 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 -0500198
199 [notification setTitle:localizedTitle];
200 [notification setSoundName:NSUserNotificationDefaultSoundName];
201 [notification setSubtitle:qIdx.data().toString().toNSString()];
202
203 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
204 });
Alexandre Lisione4041492015-03-20 18:20:43 -0400205}
206
207- (void) showIncomingNotification:(Call*) call{
Alexandre Lision34607032016-02-08 16:16:49 -0500208 NSUserNotification* notification = [[NSUserNotification alloc] init];
Alexandre Lisionc8180112016-01-27 11:27:50 -0500209 NSString* localizedTitle = [NSString stringWithFormat:
210 NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500211 [notification setTitle:localizedTitle];
212 [notification setSoundName:NSUserNotificationDefaultSoundName];
Alexandre Lisione4041492015-03-20 18:20:43 -0400213
Alexandre Lision34607032016-02-08 16:16:49 -0500214 // try to activate action button
215 @try {
216 [notification setValue:@YES forKey:@"_showsButtons"];
217 }
218 @catch (NSException *exception) {
219 // private API _showsButtons has changed...
220 NSLog(@"Action button not activable on notification");
221 }
222 [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
223
Alexandre Lisione4041492015-03-20 18:20:43 -0400224 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500225}
226
Alexandre Lision34607032016-02-08 16:16:49 -0500227- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
228{
229 if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
230 CallModel::instance().selectedCall() << Call::Action::REFUSE;
231 } else {
232 [NSApp activateIgnoringOtherApps:YES];
233 if ([self.ringWindowController.window isMiniaturized]) {
234 [self.ringWindowController.window deminiaturize:self];
235 }
236 }
237}
238
Alexandre Lision745e4d62015-03-22 20:03:10 -0400239- (void) showWizard
240{
Alexandre Lision745e4d62015-03-22 20:03:10 -0400241 if(self.wizard == nil) {
242 self.wizard = [[RingWizardWC alloc] initWithWindowNibName:@"RingWizard"];
243 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500244 [self.wizard.window makeKeyAndOrderFront:self];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400245}
246
247- (void) showMainWindow
248{
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400249 if(self.ringWindowController == nil) {
Alexandre Lision745e4d62015-03-22 20:03:10 -0400250 self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow"];
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400251 }
Kateryna Kostiuk465cfbe2018-06-05 16:13:05 -0400252 [[NSApplication sharedApplication] removeWindowsItem:self.wizard.window];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400253 [self.ringWindowController.window makeKeyAndOrderFront:self];
254}
255
Alexandre Lision62005312016-01-28 15:55:16 -0500256- (void) showDialpad
257{
258 if (self.dialpad == nil) {
259 self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
260 }
261 [self.dialpad.window makeKeyAndOrderFront:self];
262}
263
264
Alexandre Lision745e4d62015-03-22 20:03:10 -0400265- (BOOL) checkForRingAccount
266{
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400267 BOOL foundRingAcc = NO;
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400268 for (int i = 0 ; i < AccountModel::instance().rowCount() ; ++i) {
269 QModelIndex idx = AccountModel::instance().index(i);
270 Account* acc = AccountModel::instance().getAccountByModelIndex(idx);
Alexandre Lision76d59692016-01-20 18:06:05 -0500271 if(acc->protocol() == Account::Protocol::RING && !acc->isNew()) {
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400272 if (acc->displayName().isEmpty())
273 acc->setDisplayName(acc->alias());
274 foundRingAcc = YES;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400275 }
276 }
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400277 return foundRingAcc;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400278}
279
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400280-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
281{
282 NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
283 [appleEventManager setEventHandler:self
284 andSelector:@selector(handleGetURLEvent:withReplyEvent:)
285 forEventClass:kInternetEventClass andEventID:kAEGetURL];
286}
287
288/**
289 * Recognized patterns:
290 * - ring:<hash>
291 * - ring://<hash>
292 */
293- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
294{
295 NSString* query = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
296 NSURL* url = [[NSURL alloc] initWithString:query];
297 NSString* ringID = [url host];
298 if (!ringID) {
299 //not a valid NSURL, try to parse query directly
300 ringID = [query substringFromIndex:@"ring:".length];
301 }
302
303 // check for a valid ring hash
304 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
305 BOOL valid = [[ringID stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
306
307 if(valid && ringID.length == 40) {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400308 Call* c = CallModel::instance().dialingCall();
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400309 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",ringID]));
310 c << Call::Action::ACCEPT;
311 } else {
312 NSAlert *alert = [[NSAlert alloc] init];
313 [alert addButtonWithTitle:@"OK"];
314 [alert setMessageText:@"Error"];
315 [alert setInformativeText:@"ringID cannot be read from this URL."];
316 [alert setAlertStyle:NSWarningAlertStyle];
317 [alert runModal];
318 }
319}
320
Alexandre Lision745e4d62015-03-22 20:03:10 -0400321- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
322{
323 if([self checkForRingAccount]) {
324 [self showMainWindow];
325 } else {
326 [self showWizard];
327 }
328 return YES;
329}
330
Edric Milaret81315412015-05-13 15:14:56 -0400331- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
332{
Alexandre Lision76d59692016-01-20 18:06:05 -0500333 [self cleanExit];
Edric Milaret81315412015-05-13 15:14:56 -0400334}
335
336-(void)applicationWillTerminate:(NSNotification *)notification
337{
Alexandre Lision76d59692016-01-20 18:06:05 -0500338 [self cleanExit];
339}
340
341- (void) cleanExit
342{
Andreas Traczyke4d6e782018-03-22 17:51:30 -0400343 if (preventSleepTimer != nil) {
344 [preventSleepTimer invalidate];
345 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500346 [self.wizard close];
347 [self.ringWindowController close];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400348 delete CallModel::instance().QObject::parent();
Edric Milaret81315412015-05-13 15:14:56 -0400349 [[NSApplication sharedApplication] terminate:self];
350}
351
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400352#if ENABLE_SPARKLE
353
Alexandre Lision76d59692016-01-20 18:06:05 -0500354#pragma mark - Sparkle delegate
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400355
356- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
357{
358 [NSApp activateIgnoringOtherApps:YES];
359}
360
361- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
362{
363 return YES;
364}
365
366- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater
367{
368 return YES;
369}
370
371- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error
372{
373 NSLog(@"Error:%@", error.localizedDescription);
374}
375
376#endif
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500377@end