1 /* 2 * libjingle 3 * Copyright 2012, Google Inc 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 #import "talk/base/maccocoasocketserver.h" 28 29 #import <Foundation/Foundation.h> 30 #import <AppKit/AppKit.h> 31 #include <assert.h> 32 33 #include "talk/base/scoped_autorelease_pool.h" 34 35 // MacCocoaSocketServerHelper serves as a delegate to NSMachPort or a target for 36 // a timeout. 37 @interface MacCocoaSocketServerHelper : NSObject { 38 // This is a weak reference. This works fine since the 39 // talk_base::MacCocoaSocketServer owns this object. 40 talk_base::MacCocoaSocketServer* socketServer_; // Weak. 41 } 42 @end 43 44 @implementation MacCocoaSocketServerHelper 45 - (id)initWithSocketServer:(talk_base::MacCocoaSocketServer*)ss { 46 self = [super init]; 47 if (self) { 48 socketServer_ = ss; 49 } 50 return self; 51 } 52 53 - (void)timerFired:(NSTimer*)timer { 54 socketServer_->WakeUp(); 55 } 56 57 - (void)breakMainloop { 58 [NSApp stop:self]; 59 // NSApp stop only exits after finishing processing of the 60 // current event. Since we're potentially in a timer callback 61 // and not an NSEvent handler, we need to trigger a dummy one 62 // and turn the loop over. We may be able to skip this if we're 63 // on the ss' thread and not inside the app loop already. 64 NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined 65 location:NSMakePoint(0,0) 66 modifierFlags:0 67 timestamp:0 68 windowNumber:0 69 context:nil 70 subtype:0 71 data1:0 72 data2:0]; 73 [NSApp postEvent:event atStart:NO]; 74 } 75 @end 76 77 namespace talk_base { 78 79 MacCocoaSocketServer::MacCocoaSocketServer() { 80 helper_ = [[MacCocoaSocketServerHelper alloc] initWithSocketServer:this]; 81 timer_ = nil; 82 run_count_ = 0; 83 84 // Initialize the shared NSApplication 85 [NSApplication sharedApplication]; 86 } 87 88 MacCocoaSocketServer::~MacCocoaSocketServer() { 89 [timer_ invalidate]; 90 [timer_ release]; 91 [helper_ release]; 92 } 93 94 // ::Wait is reentrant, for example when blocking on another thread while 95 // responding to I/O. Calls to [NSApp] MUST be made from the main thread 96 // only! 97 bool MacCocoaSocketServer::Wait(int cms, bool process_io) { 98 talk_base::ScopedAutoreleasePool pool; 99 if (!process_io && cms == 0) { 100 // No op. 101 return true; 102 } 103 if ([NSApp isRunning]) { 104 // Only allow reentrant waiting if we're in a blocking send. 105 ASSERT(!process_io && cms == kForever); 106 } 107 108 if (!process_io) { 109 // No way to listen to common modes and not get socket events, unless 110 // we disable each one's callbacks. 111 EnableSocketCallbacks(false); 112 } 113 114 if (kForever != cms) { 115 // Install a timer that fires wakeup after cms has elapsed. 116 timer_ = 117 [NSTimer scheduledTimerWithTimeInterval:cms / 1000.0 118 target:helper_ 119 selector:@selector(timerFired:) 120 userInfo:nil 121 repeats:NO]; 122 [timer_ retain]; 123 } 124 125 // Run until WakeUp is called, which will call stop and exit this loop. 126 run_count_++; 127 [NSApp run]; 128 run_count_--; 129 130 if (!process_io) { 131 // Reenable them. Hopefully this won't cause spurious callbacks or 132 // missing ones while they were disabled. 133 EnableSocketCallbacks(true); 134 } 135 136 return true; 137 } 138 139 // Can be called from any thread. Post a message back to the main thread to 140 // break out of the NSApp loop. 141 void MacCocoaSocketServer::WakeUp() { 142 if (timer_ != nil) { 143 [timer_ invalidate]; 144 [timer_ release]; 145 timer_ = nil; 146 } 147 148 // [NSApp isRunning] returns unexpected results when called from another 149 // thread. Maintain our own count of how many times to break the main loop. 150 if (run_count_ > 0) { 151 [helper_ performSelectorOnMainThread:@selector(breakMainloop) 152 withObject:nil 153 waitUntilDone:false]; 154 } 155 } 156 157 } // namespace talk_base 158