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 60namespace rtc { 61 62MacCocoaSocketServer::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 71MacCocoaSocketServer::~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! 80bool 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. 124void 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