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