Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • releases/beta1
  • packaging
  • releases/alpha
  • 1.0.0
  • 0.2.0
  • 0.1.0
21 results

AppDelegate.mm

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    AppDelegate.mm 19.16 KiB
    /*
     *  Copyright (C) 2015-2016 Savoir-faire Linux Inc.
     *  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.
     */
    #import <SystemConfiguration/SystemConfiguration.h>
    
    #import "AppDelegate.h"
    
    //lrc
    #import <callmodel.h>
    #import <api/lrc.h>
    #import <api/newaccountmodel.h>
    #import <api/behaviorcontroller.h>
    #import <api/conversation.h>
    #import <api/newcallmodel.h>
    
    
    #if ENABLE_SPARKLE
    #import <Sparkle/Sparkle.h>
    #endif
    
    #import "Constants.h"
    #import "RingWizardWC.h"
    #import "DialpadWC.h"
    #import "utils.h"
    
    #if ENABLE_SPARKLE
    @interface AppDelegate() <SUUpdaterDelegate>
    #else
    @interface AppDelegate()
    #endif
    
    @property RingWindowController* ringWindowController;
    @property RingWizardWC* wizard;
    @property DialpadWC* dialpad;
    @property (nonatomic, strong) dispatch_queue_t scNetworkQueue;
    @property (nonatomic, assign) SCNetworkReachabilityRef currentReachability;
    @property (strong) id activity;
    
    @end
    
    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";
    
    
    @implementation AppDelegate {
    
    std::unique_ptr<lrc::api::Lrc> lrc;
    }
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    
        // 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
    
    #ifndef NDEBUG
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
    #else
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
    #endif
    //    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
    //     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSConstraintBasedLayoutLogUnsatisfiable"];
    //     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"__NSConstraintBasedLayoutLogUnsatisfiable"];
    
        [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
    
        NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
        [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
    
        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"];
        lrc = std::make_unique<lrc::api::Lrc>();
    
        if([self checkForRingAccount]) {
            [self showMainWindow];
        } else {
            [self showWizard];
        }
        [self connect];
    }
    
    - (void) beginObservingReachabilityStatus
    {
        SCNetworkReachabilityRef reachabilityRef = NULL;
    
        void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
            BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                lrc->connectivityChanged();
            }];
        };
    
        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);
    }
    
    - (void) connect
    {
        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];
    
                         });
    
        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;
                             }
                             bool isIncoming = false;
                             auto callModel = lrc->getAccountModel()
                             .getAccountInfo(accountId).callModel.get();
                             if(callModel->hasCall(conversationInfo.callId)) {
                                 isIncoming = !callModel->getCall(conversationInfo.callId).isOutgoing;
                             }
                             if(!isIncoming) {
                                 return;
                             }
                             NSString* name = bestIDForConversation(conversationInfo, *lrc->getAccountModel().getAccountInfo(accountId).conversationModel.get());
                             NSUserNotification* notification = [[NSUserNotification alloc] init];
    
                             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"];
                             }
                             @catch (NSException *exception) {
                                 NSLog(@"Action button not activable on notification");
                             }
                             [notification setUserInfo: userInfo];
                             [notification setOtherButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];
                             [notification setActionButtonTitle:NSLocalizedString(@"Accept", @"Button Action")];
                             [notification setTitle:localizedTitle];
                             [notification setSoundName:NSUserNotificationDefaultSoundName];
    
                             [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
                         });
    
        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) {
                                 return;
                             }
                             NSUserNotification* notification = [[NSUserNotification alloc] init];
    
                             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];
    
                             [notification setTitle:localizedTitle];
                             [notification setSoundName:NSUserNotificationDefaultSoundName];
                             [notification setSubtitle:@(interaction.body.c_str())];
                             [notification setUserInfo: userInfo];
    
                             [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
                         });
    }
    
    - (void)userNotificationCenter:(NSUserNotificationCenter *)center didDismissAlert:(NSUserNotification *)alert {
        // check if user click refuse on incoming call notifications
        if(alert.activationType != NSUserNotificationActivationTypeNone) {
            return;
        }
    
        auto info = alert.userInfo;
        if(!info) {
            return;
        }
        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]);
        }
    }
    
    - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
    {
        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]);
                [self.ringWindowController.window deminiaturize:self];
                [_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];
            }
        }
        [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
    }
    
    - (void) showWizard
    {
        if(self.wizard == nil) {
            self.wizard = [[RingWizardWC alloc] initWithNibName:@"RingWizard" bundle: nil accountmodel: &lrc->getAccountModel()];
        }
        [self.wizard.window makeKeyAndOrderFront:self];
    }
    
    - (void) showMainWindow
    {
        if(self.ringWindowController == nil) {
            self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow" bundle: nil accountModel:&lrc->getAccountModel() dataTransferModel:&lrc->getDataTransferModel() behaviourController:&lrc->getBehaviorController() avModel: &lrc->getAVModel()];
        }
        [[NSApplication sharedApplication] removeWindowsItem:self.wizard.window];
        [self.ringWindowController.window makeKeyAndOrderFront:self];
    }
    
    - (void) showDialpad
    {
        if (self.dialpad == nil) {
            self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
        }
        [self.dialpad.window makeKeyAndOrderFront:self];
    }
    
    
    - (BOOL) checkForRingAccount
    {
        return !lrc->getAccountModel().getAccountList().empty();
    }
    
    -(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) {
            Call* c = CallModel::instance().dialingCall();
            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];
        }
    }
    
    - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
    {
        if([self checkForRingAccount]) {
            [self showMainWindow];
        } else {
            [self showWizard];
        }
        return YES;
    }
    
    - (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
    {
        [[NSApplication sharedApplication] terminate:self];
    }
    
    -(void)applicationWillTerminate:(NSNotification *)notification
    {
        [self cleanExit];
    }
    
    - (void) cleanExit
    {
        if (self.activity != nil) {
            [[NSProcessInfo processInfo] endActivity:self.activity];
            self.activity = nil;
        }
        [self.wizard close];
        [self.ringWindowController close];
        [[NSApplication sharedApplication] terminate:self];
    }
    
    #if ENABLE_SPARKLE
    
    #pragma mark - Sparkle delegate
    
    - (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
    @end