Home | History | Annotate | Download | only in base
      1 /*
      2  *  Copyright 2012 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 #import "webrtc/base/maccocoasocketserver.h"
     11 
     12 #import <Foundation/Foundation.h>
     13 #import <AppKit/AppKit.h>
     14 #include <assert.h>
     15 
     16 #include "webrtc/base/scoped_autorelease_pool.h"
     17 
     18 // MacCocoaSocketServerHelperRtc serves as a delegate to NSMachPort or a target for
     19 // a timeout.
     20 @interface MacCocoaSocketServerHelperRtc : NSObject {
     21   // This is a weak reference. This works fine since the
     22   // rtc::MacCocoaSocketServer owns this object.
     23   rtc::MacCocoaSocketServer* socketServer_;  // Weak.
     24 }
     25 @end
     26 
     27 @implementation MacCocoaSocketServerHelperRtc
     28 - (id)initWithSocketServer:(rtc::MacCocoaSocketServer*)ss {
     29   self = [super init];
     30   if (self) {
     31     socketServer_ = ss;
     32   }
     33   return self;
     34 }
     35 
     36 - (void)timerFired:(NSTimer*)timer {
     37   socketServer_->WakeUp();
     38 }
     39 
     40 - (void)breakMainloop {
     41   [NSApp stop:self];
     42   // NSApp stop only exits after finishing processing of the
     43   // current event.  Since we're potentially in a timer callback
     44   // and not an NSEvent handler, we need to trigger a dummy one
     45   // and turn the loop over.  We may be able to skip this if we're
     46   // on the ss' thread and not inside the app loop already.
     47   NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
     48                                       location:NSMakePoint(0,0)
     49                                  modifierFlags:0
     50                                      timestamp:0
     51                                   windowNumber:0
     52                                        context:nil
     53                                        subtype:0
     54                                          data1:0
     55                                          data2:0];
     56   [NSApp postEvent:event atStart:NO];
     57 }
     58 @end
     59 
     60 namespace rtc {
     61 
     62 MacCocoaSocketServer::MacCocoaSocketServer() {
     63   helper_ = [[MacCocoaSocketServerHelperRtc alloc] initWithSocketServer:this];
     64   timer_ = nil;
     65   run_count_ = 0;
     66 
     67   // Initialize the shared NSApplication
     68   [NSApplication sharedApplication];
     69 }
     70 
     71 MacCocoaSocketServer::~MacCocoaSocketServer() {
     72   [timer_ invalidate];
     73   [timer_ release];
     74   [helper_ release];
     75 }
     76 
     77 // ::Wait is reentrant, for example when blocking on another thread while
     78 // responding to I/O. Calls to [NSApp] MUST be made from the main thread
     79 // only!
     80 bool MacCocoaSocketServer::Wait(int cms, bool process_io) {
     81   rtc::ScopedAutoreleasePool pool;
     82   if (!process_io && cms == 0) {
     83     // No op.
     84     return true;
     85   }
     86   if ([NSApp isRunning]) {
     87     // Only allow reentrant waiting if we're in a blocking send.
     88     ASSERT(!process_io && cms == kForever);
     89   }
     90 
     91   if (!process_io) {
     92     // No way to listen to common modes and not get socket events, unless
     93     // we disable each one's callbacks.
     94     EnableSocketCallbacks(false);
     95   }
     96 
     97   if (kForever != cms) {
     98     // Install a timer that fires wakeup after cms has elapsed.
     99     timer_ =
    100         [NSTimer scheduledTimerWithTimeInterval:cms / 1000.0
    101                                          target:helper_
    102                                        selector:@selector(timerFired:)
    103                                        userInfo:nil
    104                                         repeats:NO];
    105     [timer_ retain];
    106   }
    107 
    108   // Run until WakeUp is called, which will call stop and exit this loop.
    109   run_count_++;
    110   [NSApp run];
    111   run_count_--;
    112 
    113   if (!process_io) {
    114     // Reenable them.  Hopefully this won't cause spurious callbacks or
    115     // missing ones while they were disabled.
    116     EnableSocketCallbacks(true);
    117   }
    118 
    119   return true;
    120 }
    121 
    122 // Can be called from any thread.  Post a message back to the main thread to
    123 // break out of the NSApp loop.
    124 void MacCocoaSocketServer::WakeUp() {
    125   if (timer_ != nil) {
    126     [timer_ invalidate];
    127     [timer_ release];
    128     timer_ = nil;
    129   }
    130 
    131   // [NSApp isRunning] returns unexpected results when called from another
    132   // thread.  Maintain our own count of how many times to break the main loop.
    133   if (run_count_ > 0) {
    134     [helper_ performSelectorOnMainThread:@selector(breakMainloop)
    135                               withObject:nil
    136                            waitUntilDone:false];
    137   }
    138 }
    139 
    140 }  // namespace rtc
    141