blob: ac8b6303b5a1161de24247223dcee66f73fbb58b [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>
31
Alexandre Lision3d4143a2015-06-10 14:27:49 -040032#if ENABLE_SPARKLE
33#import <Sparkle/Sparkle.h>
34#endif
35
Alexandre Lisionc65310c2015-04-23 16:44:23 -040036#import "Constants.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040037#import "RingWizardWC.h"
Alexandre Lision62005312016-01-28 15:55:16 -050038#import "DialpadWC.h"
Alexandre Lision745e4d62015-03-22 20:03:10 -040039
Alexandre Lision3d4143a2015-06-10 14:27:49 -040040#if ENABLE_SPARKLE
41@interface AppDelegate() <SUUpdaterDelegate>
42#else
Alexandre Lision745e4d62015-03-22 20:03:10 -040043@interface AppDelegate()
Alexandre Lision3d4143a2015-06-10 14:27:49 -040044#endif
Alexandre Lision745e4d62015-03-22 20:03:10 -040045
46@property RingWindowController* ringWindowController;
47@property RingWizardWC* wizard;
Alexandre Lision62005312016-01-28 15:55:16 -050048@property DialpadWC* dialpad;
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040049@property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
50@property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
Alexandre Lision745e4d62015-03-22 20:03:10 -040051
52@end
53
Alexandre Lision5855b6a2015-02-03 11:31:05 -050054@implementation AppDelegate
55
56- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Alexandre Lision4a7b95e2015-02-20 10:06:43 -050057 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
58
Alexandre Lisione4041492015-03-20 18:20:43 -040059 [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
60
Edric Milaret81315412015-05-13 15:14:56 -040061 NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
62 [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
63
Alexandre Lision745e4d62015-03-22 20:03:10 -040064 if([self checkForRingAccount]) {
65 [self showMainWindow];
66 } else {
67 [self showWizard];
68 }
Alexandre Lisione4041492015-03-20 18:20:43 -040069 [self connect];
Alexandre Lisionb9f3f942016-07-23 14:29:33 -040070
71 dispatch_queue_t queue = NULL;
72 queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
73 [self setScNetworkQueue:queue];
74 [self beginObservingReachabilityStatus];
75}
76
77- (void) beginObservingReachabilityStatus
78{
79 SCNetworkReachabilityRef reachabilityRef = NULL;
80
81 void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
82 BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
83 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
84 AccountModel::instance().slotConnectivityChanged();
85 }];
86 };
87
88 SCNetworkReachabilityContext context = {
89 .version = 0,
90 .info = (void *)CFBridgingRetain(callbackBlock),
91 .release = CFRelease
92 };
93
94 reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "test");
95 if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
96 if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
97 // Remove our callback if we can't use the queue
98 SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
99 }
100 [self setCurrentReachability:reachabilityRef];
101 }
102}
103
104- (void) endObsvervingReachabilityStatusForHost:(NSString *)__unused host
105{
106 // Un-set the dispatch queue
107 if (SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
108 SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
109 }
110}
111
112static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkConnectionFlags flags, void* info)
113{
114 void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
115 callbackBlock(flags);
Alexandre Lisione4041492015-03-20 18:20:43 -0400116}
117
118- (void) connect
119{
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400120 QObject::connect(&CallModel::instance(),
Alexandre Lisione4041492015-03-20 18:20:43 -0400121 &CallModel::incomingCall,
122 [=](Call* call) {
Alexandre Lisionc65310c2015-04-23 16:44:23 -0400123 BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
124 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400125 if (shouldComeToForeground) {
Alexandre Lisione4041492015-03-20 18:20:43 -0400126 [NSApp activateIgnoringOtherApps:YES];
Alexandre Lision61d78a42015-10-06 11:22:47 -0400127 if ([self.ringWindowController.window isMiniaturized]) {
128 [self.ringWindowController.window deminiaturize:self];
129 }
130 }
Alexandre Lisione4041492015-03-20 18:20:43 -0400131
132 if(shouldNotify) {
133 [self showIncomingNotification:call];
134 }
135 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500136
137
Alexandre Lision08e72142016-01-19 13:29:36 -0500138 QObject::connect(&Media::RecordingModel::instance(),
139 &Media::RecordingModel::unreadMessagesCountChanged,
140 [=](int unreadCount) {
141 NSDockTile *tile = [[NSApplication sharedApplication] dockTile];
142 NSString* label = unreadCount ? [NSString stringWithFormat:@"%d", unreadCount]: @"";
143 [tile setBadgeLabel:label];
Alexandre Lisionef324562016-01-21 13:23:21 -0500144 [NSApp requestUserAttention:NSCriticalRequest];
Alexandre Lision08e72142016-01-19 13:29:36 -0500145 });
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500146
147 QObject::connect(&Media::RecordingModel::instance(),
148 &Media::RecordingModel::newTextMessage,
149 [=](Media::TextRecording* t, ContactMethod* cm) {
150
151 BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
152 auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);
153
154 // Don't show a notification if we are sending the text OR window already has focus OR user disabled notifications
155 if(qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction)) == Media::Media::Direction::OUT
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500156 || self.ringWindowController.window.isMainWindow || !shouldNotify)
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500157 return;
158
159 NSUserNotification* notification = [[NSUserNotification alloc] init];
160
Alexandre Lisionc8180112016-01-27 11:27:50 -0500161 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 -0500162
163 [notification setTitle:localizedTitle];
164 [notification setSoundName:NSUserNotificationDefaultSoundName];
165 [notification setSubtitle:qIdx.data().toString().toNSString()];
166
167 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
168 });
Alexandre Lisione4041492015-03-20 18:20:43 -0400169}
170
171- (void) showIncomingNotification:(Call*) call{
Alexandre Lision34607032016-02-08 16:16:49 -0500172 NSUserNotification* notification = [[NSUserNotification alloc] init];
Alexandre Lisionc8180112016-01-27 11:27:50 -0500173 NSString* localizedTitle = [NSString stringWithFormat:
174 NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
Alexandre Lision0f66bd32016-01-18 11:30:45 -0500175 [notification setTitle:localizedTitle];
176 [notification setSoundName:NSUserNotificationDefaultSoundName];
Alexandre Lisione4041492015-03-20 18:20:43 -0400177
Alexandre Lision34607032016-02-08 16:16:49 -0500178 // try to activate action button
179 @try {
180 [notification setValue:@YES forKey:@"_showsButtons"];
181 }
182 @catch (NSException *exception) {
183 // private API _showsButtons has changed...
184 NSLog(@"Action button not activable on notification");
185 }
186 [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
187
Alexandre Lisione4041492015-03-20 18:20:43 -0400188 [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500189}
190
Alexandre Lision34607032016-02-08 16:16:49 -0500191- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
192{
193 if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
194 CallModel::instance().selectedCall() << Call::Action::REFUSE;
195 } else {
196 [NSApp activateIgnoringOtherApps:YES];
197 if ([self.ringWindowController.window isMiniaturized]) {
198 [self.ringWindowController.window deminiaturize:self];
199 }
200 }
201}
202
Alexandre Lision745e4d62015-03-22 20:03:10 -0400203- (void) showWizard
204{
Alexandre Lision745e4d62015-03-22 20:03:10 -0400205 if(self.wizard == nil) {
206 self.wizard = [[RingWizardWC alloc] initWithWindowNibName:@"RingWizard"];
207 }
Alexandre Lision76d59692016-01-20 18:06:05 -0500208 [self.wizard.window makeKeyAndOrderFront:self];
Alexandre Lision745e4d62015-03-22 20:03:10 -0400209}
210
211- (void) showMainWindow
212{
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400213 if(self.ringWindowController == nil) {
Alexandre Lision745e4d62015-03-22 20:03:10 -0400214 self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow"];
Alexandre Lisionb3f7ed62015-08-17 11:53:13 -0400215 }
Alexandre Lision745e4d62015-03-22 20:03:10 -0400216 [self.ringWindowController.window makeKeyAndOrderFront:self];
217}
218
Alexandre Lision62005312016-01-28 15:55:16 -0500219- (void) showDialpad
220{
221 if (self.dialpad == nil) {
222 self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
223 }
224 [self.dialpad.window makeKeyAndOrderFront:self];
225}
226
227
Alexandre Lision745e4d62015-03-22 20:03:10 -0400228- (BOOL) checkForRingAccount
229{
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400230 BOOL foundRingAcc = NO;
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400231 for (int i = 0 ; i < AccountModel::instance().rowCount() ; ++i) {
232 QModelIndex idx = AccountModel::instance().index(i);
233 Account* acc = AccountModel::instance().getAccountByModelIndex(idx);
Alexandre Lision76d59692016-01-20 18:06:05 -0500234 if(acc->protocol() == Account::Protocol::RING && !acc->isNew()) {
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400235 if (acc->displayName().isEmpty())
236 acc->setDisplayName(acc->alias());
237 foundRingAcc = YES;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400238 }
239 }
Alexandre Lisiona1f07bf2015-07-28 10:30:55 -0400240 return foundRingAcc;
Alexandre Lision745e4d62015-03-22 20:03:10 -0400241}
242
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400243-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
244{
245 NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
246 [appleEventManager setEventHandler:self
247 andSelector:@selector(handleGetURLEvent:withReplyEvent:)
248 forEventClass:kInternetEventClass andEventID:kAEGetURL];
249}
250
251/**
252 * Recognized patterns:
253 * - ring:<hash>
254 * - ring://<hash>
255 */
256- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
257{
258 NSString* query = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
259 NSURL* url = [[NSURL alloc] initWithString:query];
260 NSString* ringID = [url host];
261 if (!ringID) {
262 //not a valid NSURL, try to parse query directly
263 ringID = [query substringFromIndex:@"ring:".length];
264 }
265
266 // check for a valid ring hash
267 NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
268 BOOL valid = [[ringID stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];
269
270 if(valid && ringID.length == 40) {
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400271 Call* c = CallModel::instance().dialingCall();
Alexandre Lision18e1fcd2015-08-04 14:38:42 -0400272 c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",ringID]));
273 c << Call::Action::ACCEPT;
274 } else {
275 NSAlert *alert = [[NSAlert alloc] init];
276 [alert addButtonWithTitle:@"OK"];
277 [alert setMessageText:@"Error"];
278 [alert setInformativeText:@"ringID cannot be read from this URL."];
279 [alert setAlertStyle:NSWarningAlertStyle];
280 [alert runModal];
281 }
282}
283
Alexandre Lision745e4d62015-03-22 20:03:10 -0400284- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
285{
286 if([self checkForRingAccount]) {
287 [self showMainWindow];
288 } else {
289 [self showWizard];
290 }
291 return YES;
292}
293
Edric Milaret81315412015-05-13 15:14:56 -0400294- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
295{
Alexandre Lision76d59692016-01-20 18:06:05 -0500296 [self cleanExit];
Edric Milaret81315412015-05-13 15:14:56 -0400297}
298
299-(void)applicationWillTerminate:(NSNotification *)notification
300{
Alexandre Lision76d59692016-01-20 18:06:05 -0500301 [self cleanExit];
302}
303
304- (void) cleanExit
305{
306 [self.wizard close];
307 [self.ringWindowController close];
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -0400308 delete CallModel::instance().QObject::parent();
Edric Milaret81315412015-05-13 15:14:56 -0400309 [[NSApplication sharedApplication] terminate:self];
310}
311
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400312#if ENABLE_SPARKLE
313
Alexandre Lision76d59692016-01-20 18:06:05 -0500314#pragma mark - Sparkle delegate
Alexandre Lision3d4143a2015-06-10 14:27:49 -0400315
316- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
317{
318 [NSApp activateIgnoringOtherApps:YES];
319}
320
321- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
322{
323 return YES;
324}
325
326- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater
327{
328 return YES;
329}
330
331- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error
332{
333 NSLog(@"Error:%@", error.localizedDescription);
334}
335
336#endif
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500337@end