AppDelegate.mm 12.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"

23
#import <callmodel.h>
Edric Milaret's avatar
Edric Milaret committed
24
#import <qapplication.h>
25 26
#import <accountmodel.h>
#import <protocolmodel.h>
27 28
#import <media/recordingmodel.h>
#import <media/textrecording.h>
29 30 31
#import <QItemSelectionModel>
#import <account.h>

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"
39

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

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

@end

Alexandre Lision's avatar
Alexandre Lision committed
54 55 56
@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Alexandre Lision's avatar
Alexandre Lision committed
57 58
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];

59 60
    [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];

Edric Milaret's avatar
Edric Milaret committed
61 62 63
    NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
    [appleEventManager setEventHandler:self andSelector:@selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];

64 65 66 67 68
    if([self checkForRingAccount]) {
        [self showMainWindow];
    } else {
        [self showWizard];
    }
69
    [self connect];
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

    dispatch_queue_t queue = NULL;
    queue = dispatch_queue_create("scNetworkReachability", DISPATCH_QUEUE_SERIAL);
    [self setScNetworkQueue:queue];
    [self beginObservingReachabilityStatus];
}

- (void) beginObservingReachabilityStatus
{
    SCNetworkReachabilityRef reachabilityRef = NULL;

    void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
        BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            AccountModel::instance().slotConnectivityChanged();
        }];
    };

    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);
116 117 118 119
}

- (void) connect
{
Alexandre Lision's avatar
Alexandre Lision committed
120
    QObject::connect(&CallModel::instance(),
121 122
                     &CallModel::incomingCall,
                     [=](Call* call) {
123 124
                         BOOL shouldComeToForeground = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::WindowBehaviour];
                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
125
                         if (shouldComeToForeground) {
126
                             [NSApp activateIgnoringOtherApps:YES];
127 128 129 130
                             if ([self.ringWindowController.window isMiniaturized]) {
                                 [self.ringWindowController.window deminiaturize:self];
                             }
                         }
131 132 133 134 135

                         if(shouldNotify) {
                             [self showIncomingNotification:call];
                         }
                     });
136 137


Alexandre Lision's avatar
Alexandre Lision committed
138 139 140 141 142 143
    QObject::connect(&Media::RecordingModel::instance(),
                     &Media::RecordingModel::unreadMessagesCountChanged,
                     [=](int unreadCount) {
                         NSDockTile *tile = [[NSApplication sharedApplication] dockTile];
                         NSString* label = unreadCount ? [NSString stringWithFormat:@"%d", unreadCount]: @"";
                         [tile setBadgeLabel:label];
Alexandre Lision's avatar
Alexandre Lision committed
144
                         [NSApp requestUserAttention:NSCriticalRequest];
Alexandre Lision's avatar
Alexandre Lision committed
145
                     });
146 147 148 149 150 151 152 153 154 155

    QObject::connect(&Media::RecordingModel::instance(),
                     &Media::RecordingModel::newTextMessage,
                     [=](Media::TextRecording* t, ContactMethod* cm) {

                         BOOL shouldNotify = [[NSUserDefaults standardUserDefaults] boolForKey:Preferences::Notifications];
                         auto qIdx = t->instantTextMessagingModel()->index(t->instantTextMessagingModel()->rowCount()-1, 0);

                         // Don't show a notification if we are sending the text OR window already has focus OR user disabled notifications
                         if(qvariant_cast<Media::Media::Direction>(qIdx.data((int)Media::TextRecording::Role::Direction)) == Media::Media::Direction::OUT
156
                            || self.ringWindowController.window.isMainWindow || !shouldNotify)
157 158 159 160
                             return;

                         NSUserNotification* notification = [[NSUserNotification alloc] init];

Alexandre Lision's avatar
Alexandre Lision committed
161
                         NSString* localizedTitle = [NSString stringWithFormat:NSLocalizedString(@"Message from %@", @"Message from {Name}"), qIdx.data((int)Media::TextRecording::Role::AuthorDisplayname).toString().toNSString()];
162 163 164 165 166 167 168

                         [notification setTitle:localizedTitle];
                         [notification setSoundName:NSUserNotificationDefaultSoundName];
                         [notification setSubtitle:qIdx.data().toString().toNSString()];

                         [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
                     });
169 170 171
}

- (void) showIncomingNotification:(Call*) call{
Alexandre Lision's avatar
Alexandre Lision committed
172
    NSUserNotification* notification = [[NSUserNotification alloc] init];
Alexandre Lision's avatar
Alexandre Lision committed
173 174
    NSString* localizedTitle = [NSString stringWithFormat:
                                NSLocalizedString(@"Incoming call from %@", @"Incoming call from {Name}"), call->peerName().toNSString()];
175 176
    [notification setTitle:localizedTitle];
    [notification setSoundName:NSUserNotificationDefaultSoundName];
177

Alexandre Lision's avatar
Alexandre Lision committed
178 179 180 181 182 183 184 185 186 187
    // try to activate action button
    @try {
        [notification setValue:@YES forKey:@"_showsButtons"];
    }
    @catch (NSException *exception) {
        // private API _showsButtons has changed...
        NSLog(@"Action button not activable on notification");
    }
    [notification setActionButtonTitle:NSLocalizedString(@"Refuse", @"Button Action")];

188
    [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
Alexandre Lision's avatar
Alexandre Lision committed
189 190
}

Alexandre Lision's avatar
Alexandre Lision committed
191 192 193 194 195 196 197 198 199 200 201 202
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
    if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
        CallModel::instance().selectedCall() << Call::Action::REFUSE;
    } else {
        [NSApp activateIgnoringOtherApps:YES];
        if ([self.ringWindowController.window isMiniaturized]) {
            [self.ringWindowController.window deminiaturize:self];
        }
    }
}

203 204 205 206 207
- (void) showWizard
{
    if(self.wizard == nil) {
        self.wizard = [[RingWizardWC alloc] initWithWindowNibName:@"RingWizard"];
    }
208
    [self.wizard.window makeKeyAndOrderFront:self];
209 210 211 212
}

- (void) showMainWindow
{
213
    if(self.ringWindowController == nil) {
214
        self.ringWindowController = [[RingWindowController alloc] initWithWindowNibName:@"RingWindow"];
215
    }
216 217 218
    [self.ringWindowController.window makeKeyAndOrderFront:self];
}

Alexandre Lision's avatar
Alexandre Lision committed
219 220 221 222 223 224 225 226 227
- (void) showDialpad
{
    if (self.dialpad == nil) {
        self.dialpad = [[DialpadWC alloc] initWithWindowNibName:@"Dialpad"];
    }
    [self.dialpad.window makeKeyAndOrderFront:self];
}


228 229
- (BOOL) checkForRingAccount
{
230
    BOOL foundRingAcc = NO;
Alexandre Lision's avatar
Alexandre Lision committed
231 232 233
    for (int i = 0 ; i < AccountModel::instance().rowCount() ; ++i) {
        QModelIndex idx = AccountModel::instance().index(i);
        Account* acc = AccountModel::instance().getAccountByModelIndex(idx);
234
        if(acc->protocol() == Account::Protocol::RING && !acc->isNew()) {
235 236 237
            if (acc->displayName().isEmpty())
                acc->setDisplayName(acc->alias());
            foundRingAcc = YES;
238 239
        }
    }
240
    return foundRingAcc;
241 242
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
-(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
271
        Call* c = CallModel::instance().dialingCall();
272 273 274 275 276 277 278 279 280 281 282 283
        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];
    }
}

284 285 286 287 288 289 290 291 292 293
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
{
    if([self checkForRingAccount]) {
        [self showMainWindow];
    } else {
        [self showWizard];
    }
    return YES;
}

Edric Milaret's avatar
Edric Milaret committed
294 295
- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
296
    [self cleanExit];
Edric Milaret's avatar
Edric Milaret committed
297 298 299 300
}

-(void)applicationWillTerminate:(NSNotification *)notification
{
301 302 303 304 305 306 307
    [self cleanExit];
}

- (void) cleanExit
{
    [self.wizard close];
    [self.ringWindowController close];
Alexandre Lision's avatar
Alexandre Lision committed
308
    delete CallModel::instance().QObject::parent();
Edric Milaret's avatar
Edric Milaret committed
309 310 311
    [[NSApplication sharedApplication] terminate:self];
}

312 313
#if ENABLE_SPARKLE

314
#pragma mark - Sparkle delegate
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336

- (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
337
@end