AppDelegate.mm 18.2 KB
Newer Older
1
/*
Alexandre Lision's avatar
Alexandre Lision committed
2
 *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */
19 20
#import <SystemConfiguration/SystemConfiguration.h>

Alexandre Lision's avatar
Alexandre Lision committed
21 22
#import "AppDelegate.h"

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
23
//lrc
24
#import <callmodel.h>
25
#import <api/lrc.h>
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
26
#import <api/newaccountmodel.h>
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
27 28 29
#import <api/behaviorcontroller.h>
#import <api/conversation.h>
#import <api/newcallmodel.h>
30

31

32 33 34 35
#if ENABLE_SPARKLE
#import <Sparkle/Sparkle.h>
#endif

36
#import "Constants.h"
37
#import "RingWizardWC.h"
Alexandre Lision's avatar
Alexandre Lision committed
38
#import "DialpadWC.h"
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
39
#import "utils.h"
40

41 42 43
#if ENABLE_SPARKLE
@interface AppDelegate() <SUUpdaterDelegate>
#else
44
@interface AppDelegate()
45
#endif
46 47 48

@property RingWindowController* ringWindowController;
@property RingWizardWC* wizard;
Alexandre Lision's avatar
Alexandre Lision committed
49
@property DialpadWC* dialpad;
50 51
@property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
@property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
52
@property (strong) id activity;
53 54 55

@end

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
56 57 58 59 60 61 62 63 64 65 66
NSString * const MESSAGE_NOTIFICATION = @"message_notification_type";
NSString * const CALL_NOTIFICATION = @"call_notification_type";
NSString * const CONTACT_REQUEST_NOTIFICATION = @"contact_request_notification_type";

NSString * const ACCOUNT_ID = @"account_id_notification_info";
NSString * const CALL_ID = @"call_id_notification_info";
NSString * const CONVERSATION_ID = @"conversation_id_notification_info";
NSString * const CONTACT_URI = @"contact_uri_notification_info";
NSString * const NOTIFICATION_TYPE = @"contact_type_notification_info";


67
@implementation AppDelegate {
Alexandre Lision's avatar
Alexandre Lision committed
68

69 70
std::unique_ptr<lrc::api::Lrc> lrc;
}
71

Alexandre Lision's avatar
Alexandre Lision committed
72
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
73 74 75 76 77 78 79 80 81 82 83

    // hide "Check for update" menu item when sparkle is disabled
    NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
    NSMenu *ringMenu = [[mainMenu itemAtIndex:0] submenu];
    NSMenuItem *updateItem = [ringMenu itemAtIndex:1];
#if ENABLE_SPARKLE
    updateItem.hidden = false;
#else
    updateItem.hidden = true;
#endif

Alexandre Lision's avatar
Alexandre Lision committed
84 85
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];

86 87
    [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];

Edric Milaret's avatar
Edric Milaret committed
88 89
    NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
    [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
90 91 92 93 94 95 96

    dispatch_queue_t queue = NULL;
    queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
    [self setScNetworkQueue:queue];
    [self beginObservingReachabilityStatus];
    NSActivityOptions options = NSActivitySuddenTerminationDisabled | NSActivityAutomaticTerminationDisabled | NSActivityBackground;
    self.activity = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:@"Receiving calls and messages"];
97
    lrc = std::make_unique<lrc::api::Lrc>();
Edric Milaret's avatar
Edric Milaret committed
98

99 100 101 102 103
    if([self checkForRingAccount]) {
        [self showMainWindow];
    } else {
        [self showWizard];
    }
104
    [self connect];
105 106 107 108 109 110 111 112 113
}

- (void) beginObservingReachabilityStatus
{
    SCNetworkReachabilityRef reachabilityRef = NULL;

    void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
        BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
114
            lrc->connectivityChanged();
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
        }];
    };

    SCNetworkReachabilityContext context = {
        .version = 0,
        .info = (void *)CFBridgingRetain(callbackBlock),
        .release = CFRelease
    };

    reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "test");
    if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
        if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
            // Remove our callback if we can't use the queue
            SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
        }
        [self setCurrentReachability:reachabilityRef];
    }
}

- (void) endObsvervingReachabilityStatusForHost:(NSString *)__unused host
{
    // Un-set the dispatch queue
    if (SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
        SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
    }
}

static void ReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkConnectionFlags flags, void* info)
{
    void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
    callbackBlock(flags);
146 147 148 149
}

- (void) connect
{
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    QObject::connect(&lrc->getBehaviorController(),
                     &lrc::api::BehaviorController::newTrustRequest,
                     [self] (const std::string& accountId, const std::string& contactUri) {
                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::ContactRequestNotifications];
                         if(!shouldNotify) {
                             return;
                         }
                         NSUserNotification* notification = [[NSUserNotification alloc] init];
                         auto contactModel = lrc->getAccountModel()
                         .getAccountInfo(accountId).contactModel.get();
                         NSString* name = contactModel->getContact(contactUri)
                         .registeredName.empty() ?
                         @(contactUri.c_str()) :
                         @(contactModel->getContact(contactUri).registeredName.c_str());
                         NSString* localizedMessage =
                         NSLocalizedString(@"Send you a contact request",
                                           @"Notification message");

                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
                         userInfo[CONTACT_URI] = @(contactUri.c_str());
                         userInfo[NOTIFICATION_TYPE] = CONTACT_REQUEST_NOTIFICATION;

                         [notification setTitle: name];
                         notification.informativeText = localizedMessage;
                         [notification setSoundName:NSUserNotificationDefaultSoundName];
                         [notification setUserInfo: userInfo];

                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
179 180 181

                     });

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
182 183 184 185 186 187 188 189 190
    QObject::connect(&lrc->getBehaviorController(),
                     &lrc::api::BehaviorController::showIncomingCallView,
                     [self] (const std::string& accountId, lrc::api::conversation::Info conversationInfo) {
                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::CallNotifications];
                         if(!shouldNotify) {
                             return;
                         }
                         NSString* name = bestIDForConversation(conversationInfo, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
                         NSUserNotification* notification = [[NSUserNotification alloc] init];
191

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
192 193 194 195 196 197 198 199 200 201 202 203
                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
                         userInfo[CALL_ID] = @(conversationInfo.callId.c_str());
                         userInfo[CONVERSATION_ID] = @(conversationInfo.uid.c_str());
                         userInfo[NOTIFICATION_TYPE] = CALL_NOTIFICATION;

                         NSString* localizedTitle = [NSString stringWithFormat:
                                                     NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"),
                                                     name];
                         // try to activate action button
                         @try {
                             [notification setValue:@YES forKey:@"_showsButtons"];
204
                         }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
205 206
                         @catch (NSException *exception) {
                             NSLog(@"Action button not activable on notification");
207
                         }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
208 209 210 211 212
                         [notification setUserInfo: userInfo];
                         [notification setOtherButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
                         [notification setActionButtonTitle:NSLocalizedString(@"Accept", @"Button Action")];
                         [notification setTitle:localizedTitle];
                         [notification setSoundName:NSUserNotificationDefaultSoundName];
213

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
214
                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
215
                     });
216

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
217 218 219 220 221 222
    QObject::connect(&lrc->getBehaviorController(),
                     &lrc::api::BehaviorController::newUnreadInteraction,
                     [self] (const std::string& accountId, const std::string& conversation,
                             uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::MessagesNotifications];
                         if(!shouldNotify) {
223
                             return;
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
224
                         }
225 226
                         NSUserNotification* notification = [[NSUserNotification alloc] init];

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
227 228 229 230 231 232 233 234 235 236 237 238 239
                         NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
                         userInfo[ACCOUNT_ID] = @(accountId.c_str());
                         userInfo[CONVERSATION_ID] = @(conversation.c_str());
                         userInfo[NOTIFICATION_TYPE] = MESSAGE_NOTIFICATION;
                         NSString* name = @(interaction.authorUri.c_str());
                         auto convIt = getConversationFromUid(conversation, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
                         auto convQueue = lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get()->allFilteredConversations();
                         if (convIt != convQueue.end()) {
                             name = bestIDForConversation(*convIt, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
                         }
                         NSString* localizedTitle = [NSString stringWithFormat:
                                                     NSLocalizedString(@"Incoming message from %@",@"Incoming message from {Name}"),
                                                     name];
240 241 242

                         [notification setTitle:localizedTitle];
                         [notification setSoundName:NSUserNotificationDefaultSoundName];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
243 244
                         [notification setSubtitle:@(interaction.body.c_str())];
                         [notification setUserInfo: userInfo];
245 246 247

                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
                     });
248 249
}

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
250 251 252 253 254
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDismissAlert:(NSUserNotification *)alert {
    // check if user click refuse on incoming call notifications
    if(alert.activationType != NSUserNotificationActivationTypeNone) {
        return;
    }
255

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
256 257 258
    auto info = alert.userInfo;
    if(!info) {
        return;
Alexandre Lision's avatar
Alexandre Lision committed
259
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
260 261 262 263 264 265 266 267 268 269 270 271
    NSString* identifier = info[NOTIFICATION_TYPE];
    NSString* callId = info[CALL_ID];
    NSString* accountId = info[ACCOUNT_ID];
    if(!identifier || !callId || !accountId) {
        return;
    }
    if([identifier isEqualToString: CALL_NOTIFICATION]) {
        auto accountInfo = &lrc->getAccountModel().getAccountInfo([accountId UTF8String]);
        if (accountInfo == nil)
            return;
        auto* callModel = accountInfo->callModel.get();
        callModel->hangUp([callId UTF8String]);
Alexandre Lision's avatar
Alexandre Lision committed
272
    }
Alexandre Lision's avatar
Alexandre Lision committed
273 274
}

Alexandre Lision's avatar
Alexandre Lision committed
275 276
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
    auto info = notification.userInfo;
    if(!info) {
        return;
    }
    NSString* identifier = info[NOTIFICATION_TYPE];
    if([identifier isEqualToString: CALL_NOTIFICATION]) {
        if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked
           || notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
            NSString* callId = info[CALL_ID];
            NSString* accountId = info[ACCOUNT_ID];
            NSString *conversationId = info[CONVERSATION_ID];
            auto accountInfo = &lrc->getAccountModel().getAccountInfo([accountId UTF8String]);
            if (accountInfo == nil)
                return;
            auto* callModel = accountInfo->callModel.get();
            callModel->accept([callId UTF8String]);
Alexandre Lision's avatar
Alexandre Lision committed
293
            [self.ringWindowController.window deminiaturize:self];
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
294 295 296 297 298 299 300 301 302 303 304 305
            [_ringWindowController showCall:callId forAccount:accountId forConversation:conversationId];
        }
    } else if(notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
        [self.ringWindowController.window deminiaturize:self];
        if([identifier isEqualToString: MESSAGE_NOTIFICATION]) {
            NSString* accountId = info[ACCOUNT_ID];
            NSString *conversationId = info[CONVERSATION_ID];
            [_ringWindowController showConversation:conversationId forAccount:accountId];
        } else if([identifier isEqualToString: CONTACT_REQUEST_NOTIFICATION]) {
            NSString* accountId = info[ACCOUNT_ID];
            NSString *contactUri = info[CONTACT_URI];
            [_ringWindowController showContactRequestFor:accountId contactUri: contactUri];
Alexandre Lision's avatar
Alexandre Lision committed
306 307
        }
    }
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
308
    [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
Alexandre Lision's avatar
Alexandre Lision committed
309 310
}

311 312 313
- (void) showWizard
{
    if(self.wizard == nil) {
314
        self.wizard = [[RingWizardWC alloc] initWithNibName:@"RingWizard" bundle: nil accountmodel: &lrc->getAccountModel()];
315
    }
316
    [self.wizard.window makeKeyAndOrderFront:self];
317 318 319 320
}

- (void) showMainWindow
{
321
    if(self.ringWindowController == nil) {
322
        self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow" bundle: nil accountModel:&lrc->getAccountModel() dataTransferModel:&lrc->getDataTransferModel() behaviourController:&lrc->getBehaviorController() avModel: &lrc->getAVModel()];
323
    }
324
    [[NSApplication sharedApplication] removeWindowsItem:self.wizard.window];
325 326 327
    [self.ringWindowController.window makeKeyAndOrderFront:self];
}

Alexandre Lision's avatar
Alexandre Lision committed
328 329 330 331 332 333 334 335 336
- (void) showDialpad
{
    if (self.dialpad == nil) {
        self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
    }
    [self.dialpad.window makeKeyAndOrderFront:self];
}


337 338
- (BOOL) checkForRingAccount
{
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
339
    return !lrc->getAccountModel().getAccountList().empty();
340 341
}

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
    [appleEventManager setEventHandler:self
                           andSelector:@selector(handleGetURLEvent:withReplyEvent:)
                         forEventClass:kInternetEventClass andEventID:kAEGetURL];
}

/**
 * Recognized patterns:
 *   - ring:<hash>
 *   - ring://<hash>
 */
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
    NSString* query = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
    NSURL* url = [[NSURL alloc] initWithString:query];
    NSString* ringID = [url host];
    if (!ringID) {
        //not a valid NSURL, try to parse query directly
        ringID = [query substringFromIndex:@"ring:".length];
    }

    // check for a valid ring hash
    NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdefABCDEF"];
    BOOL valid = [[ringID stringByTrimmingCharactersInSet:hexSet] isEqualToString:@""];

    if(valid && ringID.length == 40) {
Alexandre Lision's avatar
Alexandre Lision committed
370
        Call* c = CallModel::instance().dialingCall();
371 372 373 374 375 376 377 378 379 380 381 382
        c->setDialNumber(QString::fromNSString([NSString stringWithFormat:@"ring:%@",ringID]));
        c << Call::Action::ACCEPT;
    } else {
        NSAlert *alert = [[NSAlert alloc] init];
        [alert addButtonWithTitle:@"OK"];
        [alert setMessageText:@"Error"];
        [alert setInformativeText:@"ringID cannot be read from this URL."];
        [alert setAlertStyle:NSWarningAlertStyle];
        [alert runModal];
    }
}

383 384 385 386 387 388 389 390 391 392
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
{
    if([self checkForRingAccount]) {
        [self showMainWindow];
    } else {
        [self showWizard];
    }
    return YES;
}

Edric Milaret's avatar
Edric Milaret committed
393 394
- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
395
    [[NSApplication sharedApplication] terminate:self];
Edric Milaret's avatar
Edric Milaret committed
396 397 398 399
}

-(void)applicationWillTerminate:(NSNotification *)notification
{
400 401 402 403 404
    [self cleanExit];
}

- (void) cleanExit
{
405 406 407
    if (self.activity != nil) {
        [[NSProcessInfo processInfo] endActivity:self.activity];
        self.activity = nil;
408
    }
409 410
    [self.wizard close];
    [self.ringWindowController close];
Edric Milaret's avatar
Edric Milaret committed
411 412 413
    [[NSApplication sharedApplication] terminate:self];
}

414 415
#if ENABLE_SPARKLE

416
#pragma mark - Sparkle delegate
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438

- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
{
    [NSApp activateIgnoringOtherApps:YES];
}

- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
{
    return YES;
}

- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater
{
    return YES;
}

- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error
{
    NSLog(@"Error:%@", error.localizedDescription);
}

#endif
Alexandre Lision's avatar
Alexandre Lision committed
439
@end