1/* Classes for integration of avahi-client into NSRunLoop. 2 Copyright (C) 2010 Free Software Foundation, Inc. 3 4 Written by: Niels Grewe <niels.grewe@halbordnung.de> 5 Date: March 2010 6 7 This file is part of the GNUstep Base Library. 8 9 This library is free software; you can redistribute it and/or 10 modify it under the terms of the GNU Lesser General Public 11 License as published by the Free Software Foundation; either 12 version 2 of the License, or (at your option) any later version. 13 14 This library is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 Lesser General Public License for more details. 18 19 You should have received a copy of the GNU Lesser General Public 20 License along with this library; if not, write to the Free 21 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 Boston, MA 02110 USA. 23 */ 24 25#import "GSAvahiRunLoopIntegration.h" 26 27#define CTX(x) GSAvahiRunLoopContext *ctx = (GSAvahiRunLoopContext*)x 28@interface GSAvahiWatcher: NSObject <RunLoopEvents> 29{ 30 //The callback to call for avahi. 31 AvahiWatchCallback callback; 32 BOOL callbackInProgress; 33 AvahiWatchEvent oldEvents; 34 AvahiWatchEvent lastEvent; 35 int fileDesc; 36 GSAvahiRunLoopContext *ctx; 37 void *userData; 38} 39- (void)listenForEvents: (AvahiWatchEvent)events; 40- (AvahiWatchEvent)getEvents; 41- (void)removeFromContext; 42- (void)setContext: (GSAvahiRunLoopContext*)aCtxt; 43- (void)unschedule; 44- (void)reschedule; 45@end 46 47@implementation GSAvahiWatcher 48- (void) listenForEvents: (AvahiWatchEvent)events 49 saveState: (BOOL)saveState 50{ 51 /* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but 52 * Avahi doesn't seem to strictly require them (their Qt API doesn't handle 53 * them either). Still, it would be nice to handle AVAHI_WATCH_(ERR|HUP) 54 * here. 55 */ 56 57 // Remove old events: 58 if (!(events & AVAHI_WATCH_IN) 59 && (oldEvents & AVAHI_WATCH_IN)) 60 { 61 [[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc 62 type: ET_RDESC 63 forMode: [ctx mode] 64 all: NO]; 65 } 66 if (!(events & AVAHI_WATCH_OUT) 67 && (oldEvents & AVAHI_WATCH_OUT)) 68 { 69 [[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc 70 type: ET_WDESC 71 forMode: [ctx mode] 72 all: NO]; 73 } 74 75 // Remember event state: 76 if (saveState) 77 { 78 oldEvents = events; 79 } 80 81 // Dispatch new events to the runLoop: 82 if (events & AVAHI_WATCH_IN) 83 { 84 [[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc 85 type: ET_RDESC 86 watcher: self 87 forMode: [ctx mode]]; 88 } 89 if (events & AVAHI_WATCH_OUT) 90 { 91 [[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc 92 type: ET_WDESC 93 watcher: self 94 forMode: [ctx mode]]; 95 } 96} 97 98- (void) listenForEvents: (AvahiWatchEvent)events 99{ 100 [self listenForEvents: events 101 saveState: YES]; 102} 103 104- (void) unschedule 105{ 106 /* Don't save the new event state (i.e. no events) if we are unscheduling 107 * the watcher. We might want to reschedule it with the prior state. 108 */ 109 [self listenForEvents: (AvahiWatchEvent)0 110 saveState: NO]; 111} 112 113- (void) reschedule 114{ 115 [self listenForEvents: oldEvents 116 saveState: NO]; 117} 118 119- (id) initWithCallback: (AvahiWatchCallback)cback 120 andContext: (GSAvahiRunLoopContext*)aCtx 121 onEvent: (AvahiWatchEvent)someEvents 122 forFd: (int)fd 123 userData: (void*)ud 124{ 125 if (nil == (self = [super init])) 126 { 127 return nil; 128 } 129 fileDesc = fd; 130 // The context retains its watchers and timers: 131 ctx = aCtx; 132 callback = cback; 133 userData = ud; 134 [self listenForEvents: someEvents]; 135 return self; 136} 137 138- (AvahiWatchEvent) getEvents 139{ 140 if (callbackInProgress) 141 { 142 return (AvahiWatchEvent)0; 143 } 144 return lastEvent; 145} 146 147- (void) removeFromContext 148{ 149 [self unschedule]; 150 [ctx removeWatcher: self]; 151 // Don't reference the context anymore, since it won't have any chance of 152 // notifying us if it goes away. 153 ctx = nil; 154} 155 156- (void) receivedEvent: (void*)data 157 type: (RunLoopEventType)type 158 extra: (void*)extra 159 forMode: (NSString*)mode 160{ 161 int fd = (int)(intptr_t)data; 162 163 if (fileDesc != fd) 164 { 165 //Not good 166 return; 167 } 168 169 /* FIXME ... in the following switch, as well as setting lastEvent the 170 * code was clearing the corresponding bit in the oldEvents bitmask. 171 * This was causng a crash becasue it meant that we didn't unregister 172 * the event watcher from the run loop before deallocating it and a 173 * new incoming event was sent to the deallocated instance. 174 * I therefore removed that code, but can't see what it was intended 175 * to do. 176 */ 177 switch (type) 178 { 179 case ET_RDESC: 180 lastEvent = AVAHI_WATCH_IN; 181 break; 182 case ET_WDESC: 183 lastEvent = AVAHI_WATCH_OUT; 184 break; 185 default: 186 return; 187 } 188 189 /* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but 190 * Avahi doesn't seem to strictly require them (their Qt API doesn't handle 191 * them either). 192 */ 193 callbackInProgress = YES; 194 callback((AvahiWatch*)self, fd, lastEvent, userData); 195 callbackInProgress = NO; 196} 197 198- (void) setContext: (GSAvahiRunLoopContext*)aCtxt 199{ 200 ctx = aCtxt; 201} 202 203- (void) dealloc 204{ 205 // Remove all leftover event-handlers from the runLoop: 206 [self listenForEvents: (AvahiWatchEvent)0]; 207 [super dealloc]; 208} 209@end 210 211 212@interface GSAvahiTimer: NSObject 213{ 214 GSAvahiRunLoopContext *ctx; 215 AvahiTimeoutCallback callback; 216 NSTimer *timer; 217 NSDate *fireDate; 218 void *userData; 219} 220@end 221 222@implementation GSAvahiTimer 223- (void) didTimeout: (NSTimer*)timer 224{ 225 callback((AvahiTimeout*)self, userData); 226} 227 228- (void) setTimerToInterval: (NSTimeInterval)interval 229{ 230 // Invalidate the old timer; 231 if (timer != nil) 232 { 233 [timer invalidate]; 234 timer = nil; 235 } 236 237 // NOTE: the timer ivar is a weak reference; runloops retain their 238 // timers. 239 timer = [NSTimer timerWithTimeInterval: interval 240 target: self 241 selector: @selector(didTimeout:) 242 userInfo: nil 243 repeats: NO]; 244 [[ctx runLoop] addTimer: timer 245 forMode: [ctx mode]]; 246} 247 248- (void) setTimerToTimeval: (const struct timeval*)tv 249{ 250 // Invalidate the old timer 251 if (timer != nil) 252 { 253 [timer invalidate]; 254 timer = nil; 255 } 256 257 if (NULL != tv) 258 { 259 // Construct a NSTimeInterval for the timer: 260 NSTimeInterval interval = (NSTimeInterval)tv->tv_sec; 261 interval += (NSTimeInterval)(tv->tv_usec / 1000000.0); 262 [self setTimerToInterval: interval]; 263 } 264} 265- (id) initWithCallback: (AvahiTimeoutCallback)aCallback 266 andContext: (GSAvahiRunLoopContext*)aCtx 267 forTimeval: (const struct timeval*)tv 268 userData: (void*)ud 269{ 270 if (nil == (self = [super init])) 271 { 272 return nil; 273 } 274 // The context retains its watchers and timeouts: 275 ctx = aCtx; 276 callback = aCallback; 277 userData = ud; 278 [self setTimerToTimeval: tv]; 279 return self; 280} 281 282- (void) unschedule 283{ 284 if ([timer isValid]) 285 { 286 fireDate = [[timer fireDate] retain]; 287 [timer invalidate]; 288 timer = nil; 289 } 290} 291 292- (void) removeFromContext 293{ 294 [self unschedule]; 295 [ctx removeTimeout: self]; 296 ctx = nil; 297} 298 299- (void) setContext: (GSAvahiRunLoopContext*)aCtxt 300{ 301 ctx = aCtxt; 302} 303 304- (void) reschedule 305{ 306 // Only reschedule if fireDate has been set, otherwise the Avahi layer will 307 // schedule a new timer. 308 if (nil != fireDate) 309 { 310 NSTimeInterval interval = [fireDate timeIntervalSinceNow]; 311 [self setTimerToInterval: MAX(0.1,interval)]; 312 [fireDate release]; 313 fireDate = nil; 314 } 315} 316 317- (void) dealloc 318{ 319 if (nil != timer) 320 { 321 [timer invalidate]; 322 } 323 [super dealloc]; 324} 325@end 326 327static AvahiWatch* 328GSAvahiWatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent 329 event, AvahiWatchCallback callback, void *userData) 330{ 331 // NOTE: strangly enough, the userData parameter is not the userdata we 332 // passed to the poll structure (it is somehow related to the dbus 333 // internals). 334 CTX(api->userdata); 335 GSAvahiWatcher *w = [ctx avahiWatcherWithCallback: callback 336 onEvent: event 337 forFileDescriptor: fd 338 userData: userData]; 339 // NOTE: avahi defines AvahiWatch as a struct, since we only pass around 340 // pointers to those, we can just cast the pointer to our watcher object to 341 // AvahiWatch*. 342 return (AvahiWatch*)w; 343} 344 345static void 346GSAvahiWatchUpdate(AvahiWatch *watch, AvahiWatchEvent event) 347{ 348 [(GSAvahiWatcher*)watch listenForEvents: event]; 349} 350 351static AvahiWatchEvent 352GSAvahiWatchGetEvents(AvahiWatch *watch) 353{ 354 return [(GSAvahiWatcher*)watch getEvents]; 355} 356 357static void 358GSAvahiWatchFree(AvahiWatch *watch) 359{ 360 [(GSAvahiWatcher*)watch removeFromContext]; 361} 362 363static AvahiTimeout* 364GSAvahiTimeoutNew(const AvahiPoll *api, 365 const struct timeval *tv, AvahiTimeoutCallback callback, void *userData) 366{ 367 // NOTE: strangly enough, the userData parameter is not the userdata we 368 // passed to the poll structure (it is somehow related to the dbus 369 // internals.) 370 CTX(api->userdata); 371 GSAvahiTimer *t = [ctx avahiTimerWithCallback: callback 372 withTimeval: tv 373 userData: userData]; 374 // NOTE: Cf. GSAvahiWatchNew(). 375 return (AvahiTimeout*)t; 376} 377 378static void 379GSAvahiTimeoutUpdate(AvahiTimeout* timeout, 380 const struct timeval *tv) 381{ 382 [(GSAvahiTimer*)timeout setTimerToTimeval: tv]; 383} 384 385static void 386GSAvahiTimeoutFree(AvahiTimeout* timeout) 387{ 388 [(GSAvahiTimer*)timeout removeFromContext]; 389} 390 391@implementation GSAvahiRunLoopContext 392- (id) initWithRunLoop: (NSRunLoop*)rl 393 forMode: (NSString*)aMode 394{ 395 if (nil == (self = [super init])) 396 { 397 return nil; 398 } 399 lock = [[NSLock alloc] init]; 400 [lock setName: @"GSAvahiRunLoopContextLock"]; 401 poll = malloc(sizeof(AvahiPoll)); 402 NSAssert(poll, @"Could not allocate avahi polling structure."); 403 poll->userdata = (void*)self; //userInfo 404 poll->watch_new = GSAvahiWatchNew; //create a new GSAvahiWatcher 405 poll->watch_update = GSAvahiWatchUpdate; //update the watcher 406 poll->watch_get_events = GSAvahiWatchGetEvents; //retrieve events 407 poll->watch_free = GSAvahiWatchFree; //remove watcher from context 408 poll->timeout_new = GSAvahiTimeoutNew; //create a new GSAvahiTimer 409 poll->timeout_update = GSAvahiTimeoutUpdate; //update the timer 410 poll->timeout_free = GSAvahiTimeoutFree; //remove the timer from context 411 //Runloops don't need to be retained; 412 runLoop = rl; 413 ASSIGNCOPY(mode,aMode); 414 children = [[NSMutableArray alloc] init]; 415 return self; 416} 417 418- (NSRunLoop*) runLoop 419{ 420 // NOTE: We don't protect this with the lock because it will only ever be 421 // changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:, 422 // which is where we do the locking. 423 return runLoop; 424} 425 426- (NSString*) mode 427{ 428 /* NOTE: We don't protect this with the lock because it will only ever be 429 * changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:, 430 * which is where we do the locking. 431 */ 432 return mode; 433} 434 435- (const AvahiPoll*) avahiPoll 436{ 437 return (const AvahiPoll*)poll; 438} 439 440- (GSAvahiTimer*) avahiTimerWithCallback: (AvahiTimeoutCallback)callback 441 withTimeval: (const struct timeval*)tv 442 userData: (void*)ud 443{ 444 GSAvahiTimer *timer = nil; 445 [lock lock]; 446 timer = [[[GSAvahiTimer alloc] initWithCallback: callback 447 andContext: self 448 forTimeval: tv 449 userData: ud] autorelease]; 450 if (nil != timer) 451 { 452 [children addObject: timer]; 453 } 454 [lock unlock]; 455 return timer; 456} 457 458- (GSAvahiWatcher*) avahiWatcherWithCallback: (AvahiWatchCallback)callback 459 onEvent: (AvahiWatchEvent)someEvents 460 forFileDescriptor: (NSInteger)fd 461 userData: (void*)ud 462{ 463 GSAvahiWatcher *w = nil; 464 465 [lock lock]; 466 w = [[[GSAvahiWatcher alloc] initWithCallback: callback 467 andContext: self 468 onEvent: someEvents 469 forFd: fd 470 userData: ud] autorelease]; 471 472 if (nil != w) 473 { 474 [children addObject: w]; 475 } 476 [lock unlock]; 477 return w; 478} 479 480- (void) removeChild: (id)c 481{ 482 if (nil != c) 483 { 484 [lock lock]; 485 [children removeObject: c]; 486 [lock unlock]; 487 } 488} 489 490- (void) removeWatcher: (GSAvahiWatcher*)w 491{ 492 [self removeChild: w]; 493} 494 495- (void) removeTimeout: (GSAvahiTimer*)at 496{ 497 [self removeChild: at]; 498} 499 500- (void) removeFromRunLoop: (NSRunLoop*)rl 501 forMode: (NSString*)m 502{ 503 [lock lock]; 504 if ((rl == runLoop) && [mode isEqualToString: m]) 505 { 506 FOR_IN(GSAvahiWatcher*, child, children) 507 { 508 [child unschedule]; 509 } 510 END_FOR_IN(children) 511 runLoop = nil; 512 [mode release]; 513 mode = nil; 514 } 515 [lock unlock]; 516} 517 518- (void) scheduleInRunLoop: (NSRunLoop*)rl 519 forMode: (NSString*)m 520{ 521 [lock lock]; 522 if ((runLoop == nil) && (mode == nil) 523 && ((rl != nil) && (m != nil))) 524 { 525 runLoop = rl; 526 ASSIGNCOPY(mode,m); 527 FOR_IN(GSAvahiWatcher*, child, children) 528 { 529 [child reschedule]; 530 } 531 END_FOR_IN(children) 532 } 533 [lock unlock]; 534} 535 536- (void) dealloc 537{ 538 /* Some avahi internals might still reference the poll structure and could 539 * try to create additional watchers and timers on the runloop, so we should 540 * clean it up properly: 541 */ 542 poll->userdata = (void*)NULL; 543 [self removeFromRunLoop: runLoop 544 forMode: mode]; 545 FOR_IN(GSAvahiWatcher*, child, children) 546 { 547 [child setContext: nil]; 548 } 549 END_FOR_IN(children) 550 free(poll); 551 poll = NULL; 552 [children release]; 553 [mode release]; 554 [lock release]; 555 [super dealloc]; 556} 557@end 558