1 /* 2 * Copyright 2006, Haiku. 3 * 4 * Copyright (c) 2002-2004 Matthijs Hollemans 5 * Distributed under the terms of the MIT License. 6 * 7 * Authors: 8 * Matthijs Hollemans 9 */ 10 11 #include "debug.h" 12 #include <MidiConsumer.h> 13 #include <MidiProducer.h> 14 #include <MidiRoster.h> 15 #include "MidiRosterLooper.h" 16 #include "protocol.h" 17 18 using namespace BPrivate; 19 20 21 BMidiRosterLooper::BMidiRosterLooper() 22 : BLooper("MidiRosterLooper") 23 { 24 fInitLock = -1; 25 fRoster = NULL; 26 fWatcher = NULL; 27 } 28 29 30 BMidiRosterLooper::~BMidiRosterLooper() 31 { 32 StopWatching(); 33 34 if (fInitLock >= B_OK) { 35 delete_sem(fInitLock); 36 } 37 38 // At this point, our list may still contain endpoints with a 39 // zero reference count. These objects are proxies for remote 40 // endpoints, so we can safely delete them. If the list also 41 // contains endpoints with a non-zero refcount (which can be 42 // either remote or local), we will output a warning message. 43 // It would have been better to jump into the debugger, but I 44 // did not want to risk breaking any (misbehaving) old apps. 45 46 for (int32 t = 0; t < CountEndpoints(); ++t) { 47 BMidiEndpoint* endp = EndpointAt(t); 48 if (endp->fRefCount > 0) { 49 fprintf( 50 stderr, "[midi] WARNING: Endpoint %ld (%p) has " 51 "not been Release()d properly (refcount = %ld)\n", 52 endp->ID(), endp, endp->fRefCount); 53 } else { 54 delete endp; 55 } 56 } 57 } 58 59 60 bool 61 BMidiRosterLooper::Init(BMidiRoster* roster_) 62 { 63 ASSERT(roster_ != NULL) 64 65 fRoster = roster_; 66 67 // We create a semaphore with a zero count. BMidiRoster's 68 // MidiRoster() method will try to acquire this semaphore, 69 // but blocks because the count is 0. When we receive the 70 // "app registered" message in our MessageReceived() hook, 71 // we release the semaphore and MidiRoster() will unblock. 72 73 fInitLock = create_sem(0, "InitLock"); 74 75 if (fInitLock < B_OK) { 76 WARN("Could not create semaphore") 77 return false; 78 } 79 80 thread_id threadId = Run(); 81 82 if (threadId < B_OK) { 83 WARN("Could not start looper thread") 84 return false; 85 } 86 87 return true; 88 } 89 90 91 BMidiEndpoint* 92 BMidiRosterLooper::NextEndpoint(int32* id) 93 { 94 ASSERT(id != NULL) 95 96 for (int32 t = 0; t < CountEndpoints(); ++t) { 97 BMidiEndpoint* endp = EndpointAt(t); 98 if (endp->ID() > *id) { 99 if (endp->IsRemote() && endp->IsRegistered()) { 100 *id = endp->ID(); 101 return endp; 102 } 103 } 104 } 105 106 return NULL; 107 } 108 109 110 BMidiEndpoint* 111 BMidiRosterLooper::FindEndpoint(int32 id) 112 { 113 for (int32 t = 0; t < CountEndpoints(); ++t) { 114 BMidiEndpoint* endp = EndpointAt(t); 115 if (endp->ID() == id) { 116 return endp; 117 } 118 } 119 120 return NULL; 121 } 122 123 124 void 125 BMidiRosterLooper::AddEndpoint(BMidiEndpoint* endp) 126 { 127 ASSERT(endp != NULL) 128 ASSERT(!fEndpoints.HasItem(endp)) 129 130 // We store the endpoints sorted by ID, because that 131 // simplifies the implementation of NextEndpoint(). 132 // Although the midi_server assigns IDs in ascending 133 // order, we can't assume that the mNEW messages also 134 // are delivered in this order (mostly they will be). 135 136 int32 t; 137 for (t = CountEndpoints(); t > 0; --t) { 138 BMidiEndpoint* other = EndpointAt(t - 1); 139 if (endp->ID() > other->ID()) { 140 break; 141 } 142 } 143 fEndpoints.AddItem(endp, t); 144 145 #ifdef DEBUG 146 DumpEndpoints(); 147 #endif 148 } 149 150 151 void 152 BMidiRosterLooper::RemoveEndpoint(BMidiEndpoint* endp) 153 { 154 ASSERT(endp != NULL) 155 ASSERT(fEndpoints.HasItem(endp)) 156 157 fEndpoints.RemoveItem(endp); 158 159 if (endp->IsConsumer()) { 160 DisconnectDeadConsumer((BMidiConsumer*) endp); 161 } else { 162 DisconnectDeadProducer((BMidiProducer*) endp); 163 } 164 165 #ifdef DEBUG 166 DumpEndpoints(); 167 #endif 168 } 169 170 171 void 172 BMidiRosterLooper::StartWatching(const BMessenger* watcher_) 173 { 174 ASSERT(watcher_ != NULL) 175 176 StopWatching(); 177 fWatcher = new BMessenger(*watcher_); 178 179 AllEndpoints(); 180 AllConnections(); 181 } 182 183 184 void 185 BMidiRosterLooper::StopWatching() 186 { 187 delete fWatcher; 188 fWatcher = NULL; 189 } 190 191 192 void 193 BMidiRosterLooper::MessageReceived(BMessage* msg) 194 { 195 #ifdef DEBUG 196 printf("IN "); msg->PrintToStream(); 197 #endif 198 199 switch (msg->what) { 200 case MSG_APP_REGISTERED: OnAppRegistered(msg); break; 201 case MSG_ENDPOINT_CREATED: OnEndpointCreated(msg); break; 202 case MSG_ENDPOINT_DELETED: OnEndpointDeleted(msg); break; 203 case MSG_ENDPOINT_CHANGED: OnEndpointChanged(msg); break; 204 case MSG_ENDPOINTS_CONNECTED: OnConnectedDisconnected(msg); break; 205 case MSG_ENDPOINTS_DISCONNECTED: OnConnectedDisconnected(msg); break; 206 207 default: super::MessageReceived(msg); break; 208 } 209 } 210 211 212 void 213 BMidiRosterLooper::OnAppRegistered(BMessage* msg) 214 { 215 release_sem(fInitLock); 216 } 217 218 219 void 220 BMidiRosterLooper::OnEndpointCreated(BMessage* msg) 221 { 222 int32 id; 223 bool isRegistered; 224 BString name; 225 BMessage properties; 226 bool isConsumer; 227 228 if ((msg->FindInt32("midi:id", &id) == B_OK) 229 && (msg->FindBool("midi:registered", &isRegistered) == B_OK) 230 && (msg->FindString("midi:name", &name) == B_OK) 231 && (msg->FindMessage("midi:properties", &properties) == B_OK) 232 && (msg->FindBool("midi:consumer", &isConsumer) == B_OK)) { 233 if (isConsumer) { 234 int32 port; 235 bigtime_t latency; 236 237 if ((msg->FindInt32("midi:port", &port) == B_OK) 238 && (msg->FindInt64("midi:latency", &latency) == B_OK)) { 239 BMidiConsumer* cons = new BMidiConsumer(); 240 cons->fName = name; 241 cons->fId = id; 242 cons->fIsRegistered = isRegistered; 243 cons->fPort = port; 244 cons->fLatency = latency; 245 *(cons->fProperties) = properties; 246 AddEndpoint(cons); 247 return; 248 } 249 } else { // producer 250 BMidiProducer* prod = new BMidiProducer(); 251 prod->fName = name; 252 prod->fId = id; 253 prod->fIsRegistered = isRegistered; 254 *(prod->fProperties) = properties; 255 AddEndpoint(prod); 256 return; 257 } 258 } 259 260 WARN("Could not create proxy for remote endpoint") 261 } 262 263 264 void 265 BMidiRosterLooper::OnEndpointDeleted(BMessage* msg) 266 { 267 int32 id; 268 if (msg->FindInt32("midi:id", &id) == B_OK) { 269 BMidiEndpoint* endp = FindEndpoint(id); 270 if (endp != NULL) { 271 RemoveEndpoint(endp); 272 273 // If the client is watching, and the endpoint is 274 // registered remote, we need to let it know that 275 // the endpoint is now unregistered. 276 277 if (endp->IsRemote() && endp->IsRegistered()) { 278 if (fWatcher != NULL) { 279 BMessage notify; 280 notify.AddInt32("be:op", B_MIDI_UNREGISTERED); 281 ChangeEvent(¬ify, endp); 282 } 283 } 284 285 // If the proxy object for this endpoint is no 286 // longer being used, we can delete it. However, 287 // if the refcount is not zero, we must defer 288 // destruction until the client Release()'s the 289 // object. We clear the "isRegistered" flag to 290 // let the client know the object is now invalid. 291 292 if (endp->fRefCount == 0) { 293 delete endp; 294 } else { // still being used 295 endp->fIsRegistered = false; 296 endp->fIsAlive = false; 297 } 298 299 return; 300 } 301 } 302 303 WARN("Could not delete proxy for remote endpoint") 304 } 305 306 307 void 308 BMidiRosterLooper::OnEndpointChanged(BMessage* msg) 309 { 310 int32 id; 311 if (msg->FindInt32("midi:id", &id) == B_OK) { 312 BMidiEndpoint* endp = FindEndpoint(id); 313 if ((endp != NULL) && endp->IsRemote()) { 314 ChangeRegistered(msg, endp); 315 ChangeName(msg, endp); 316 ChangeProperties(msg, endp); 317 ChangeLatency(msg, endp); 318 319 #ifdef DEBUG 320 DumpEndpoints(); 321 #endif 322 323 return; 324 } 325 } 326 327 WARN("Could not change endpoint attributes") 328 } 329 330 331 void 332 BMidiRosterLooper::OnConnectedDisconnected(BMessage* msg) 333 { 334 int32 prodId, consId; 335 if ((msg->FindInt32("midi:producer", &prodId) == B_OK) 336 && (msg->FindInt32("midi:consumer", &consId) == B_OK)) { 337 BMidiEndpoint* endp1 = FindEndpoint(prodId); 338 BMidiEndpoint* endp2 = FindEndpoint(consId); 339 340 if ((endp1 != NULL) && endp1->IsProducer()) { 341 if ((endp2 != NULL) && endp2->IsConsumer()) { 342 BMidiProducer* prod = (BMidiProducer*) endp1; 343 BMidiConsumer* cons = (BMidiConsumer*) endp2; 344 345 bool mustConnect = (msg->what == MSG_ENDPOINTS_CONNECTED); 346 347 if (mustConnect) { 348 prod->ConnectionMade(cons); 349 } else { 350 prod->ConnectionBroken(cons); 351 } 352 353 if (fWatcher != NULL) { 354 ConnectionEvent(prod, cons, mustConnect); 355 } 356 357 #ifdef DEBUG 358 DumpEndpoints(); 359 #endif 360 361 return; 362 } 363 } 364 } 365 366 WARN("Could not connect/disconnect endpoints") 367 } 368 369 370 void 371 BMidiRosterLooper::ChangeRegistered(BMessage* msg, BMidiEndpoint* endp) 372 { 373 ASSERT(msg != NULL) 374 ASSERT(endp != NULL) 375 376 bool isRegistered; 377 if (msg->FindBool("midi:registered", &isRegistered) == B_OK) { 378 if (endp->fIsRegistered != isRegistered) { 379 endp->fIsRegistered = isRegistered; 380 381 if (fWatcher != NULL) { 382 BMessage notify; 383 if (isRegistered) { 384 notify.AddInt32("be:op", B_MIDI_REGISTERED); 385 } else { 386 notify.AddInt32("be:op", B_MIDI_UNREGISTERED); 387 } 388 ChangeEvent(¬ify, endp); 389 } 390 } 391 } 392 } 393 394 395 void 396 BMidiRosterLooper::ChangeName(BMessage* msg, BMidiEndpoint* endp) 397 { 398 ASSERT(msg != NULL) 399 ASSERT(endp != NULL) 400 401 BString name; 402 if (msg->FindString("midi:name", &name) == B_OK) { 403 if (endp->fName != name) { 404 endp->fName = name; 405 406 if ((fWatcher != NULL) && endp->IsRegistered()) { 407 BMessage notify; 408 notify.AddInt32("be:op", B_MIDI_CHANGED_NAME); 409 notify.AddString("be:name", name); 410 ChangeEvent(¬ify, endp); 411 } 412 } 413 } 414 } 415 416 417 void 418 BMidiRosterLooper::ChangeProperties(BMessage* msg, BMidiEndpoint* endp) 419 { 420 ASSERT(msg != NULL) 421 ASSERT(endp != NULL) 422 423 BMessage properties; 424 if (msg->FindMessage("midi:properties", &properties) == B_OK) { 425 *(endp->fProperties) = properties; 426 427 if ((fWatcher != NULL) && endp->IsRegistered()) { 428 BMessage notify; 429 notify.AddInt32("be:op", B_MIDI_CHANGED_PROPERTIES); 430 notify.AddMessage("be:properties", &properties); 431 ChangeEvent(¬ify, endp); 432 } 433 } 434 } 435 436 437 void 438 BMidiRosterLooper::ChangeLatency(BMessage* msg, BMidiEndpoint* endp) 439 { 440 ASSERT(msg != NULL) 441 ASSERT(endp != NULL) 442 443 bigtime_t latency; 444 if (msg->FindInt64("midi:latency", &latency) == B_OK) { 445 if (endp->IsConsumer()) { 446 BMidiConsumer* cons = (BMidiConsumer*) endp; 447 if (cons->fLatency != latency) { 448 cons->fLatency = latency; 449 450 if ((fWatcher != NULL) && cons->IsRegistered()) { 451 BMessage notify; 452 notify.AddInt32("be:op", B_MIDI_CHANGED_LATENCY); 453 notify.AddInt64("be:latency", latency); 454 ChangeEvent(¬ify, endp); 455 } 456 } 457 } 458 } 459 } 460 461 462 void 463 BMidiRosterLooper::AllEndpoints() 464 { 465 BMessage notify; 466 for (int32 t = 0; t < CountEndpoints(); ++t) { 467 BMidiEndpoint* endp = EndpointAt(t); 468 if (endp->IsRemote() && endp->IsRegistered()) { 469 notify.MakeEmpty(); 470 notify.AddInt32("be:op", B_MIDI_REGISTERED); 471 ChangeEvent(¬ify, endp); 472 } 473 } 474 } 475 476 477 void 478 BMidiRosterLooper::AllConnections() 479 { 480 for (int32 t = 0; t < CountEndpoints(); ++t) { 481 BMidiEndpoint* endp = EndpointAt(t); 482 if (endp->IsRemote() && endp->IsRegistered()) { 483 if (endp->IsProducer()) { 484 BMidiProducer* prod = (BMidiProducer*) endp; 485 if (prod->LockProducer()) { 486 for (int32 k = 0; k < prod->CountConsumers(); ++k) { 487 ConnectionEvent(prod, prod->ConsumerAt(k), true); 488 } 489 prod->UnlockProducer(); 490 } 491 } 492 } 493 } 494 } 495 496 497 void 498 BMidiRosterLooper::ChangeEvent(BMessage* msg, BMidiEndpoint* endp) 499 { 500 ASSERT(fWatcher != NULL) 501 ASSERT(msg != NULL) 502 ASSERT(endp != NULL) 503 504 msg->what = B_MIDI_EVENT; 505 msg->AddInt32("be:id", endp->ID()); 506 507 if (endp->IsConsumer()) { 508 msg->AddString("be:type", "consumer"); 509 } else { 510 msg->AddString("be:type", "producer"); 511 } 512 513 fWatcher->SendMessage(msg); 514 } 515 516 517 void 518 BMidiRosterLooper::ConnectionEvent( 519 BMidiProducer* prod, BMidiConsumer* cons, bool mustConnect) 520 { 521 ASSERT(fWatcher != NULL) 522 ASSERT(prod != NULL) 523 ASSERT(cons != NULL) 524 525 BMessage notify; 526 notify.what = B_MIDI_EVENT; 527 notify.AddInt32("be:producer", prod->ID()); 528 notify.AddInt32("be:consumer", cons->ID()); 529 530 if (mustConnect) { 531 notify.AddInt32("be:op", B_MIDI_CONNECTED); 532 } else { 533 notify.AddInt32("be:op", B_MIDI_DISCONNECTED); 534 } 535 536 fWatcher->SendMessage(¬ify); 537 } 538 539 540 void 541 BMidiRosterLooper::DisconnectDeadConsumer(BMidiConsumer* cons) 542 { 543 ASSERT(cons != NULL) 544 545 // Note: Rather than looping through each producer's list 546 // of connected consumers, we let ConnectionBroken() tell 547 // us whether the consumer really was connected. 548 549 for (int32 t = 0; t < CountEndpoints(); ++t) { 550 BMidiEndpoint* endp = EndpointAt(t); 551 if (endp->IsProducer()) { 552 BMidiProducer* prod = (BMidiProducer*) endp; 553 if (prod->ConnectionBroken(cons)) { 554 if (cons->IsRemote() && (fWatcher != NULL)) { 555 ConnectionEvent(prod, cons, false); 556 } 557 } 558 } 559 } 560 } 561 562 563 void 564 BMidiRosterLooper::DisconnectDeadProducer(BMidiProducer* prod) 565 { 566 ASSERT(prod != NULL) 567 568 // We don't need to lock or remove the consumers from 569 // the producer's list of connections, because when this 570 // function is called, we're destroying the object. 571 572 if (prod->IsRemote() && (fWatcher != NULL)) { 573 for (int32 t = 0; t < prod->CountConsumers(); ++t) { 574 ConnectionEvent(prod, prod->ConsumerAt(t), false); 575 } 576 } 577 } 578 579 580 int32 581 BMidiRosterLooper::CountEndpoints() 582 { 583 return fEndpoints.CountItems(); 584 } 585 586 587 BMidiEndpoint* 588 BMidiRosterLooper::EndpointAt(int32 index) 589 { 590 ASSERT(index >= 0 && index < CountEndpoints()) 591 592 return (BMidiEndpoint*) fEndpoints.ItemAt(index); 593 } 594 595 596 #ifdef DEBUG 597 void 598 BMidiRosterLooper::DumpEndpoints() 599 { 600 if (Lock()) { 601 printf("*** START DumpEndpoints\n"); 602 603 for (int32 t = 0; t < CountEndpoints(); ++t) { 604 BMidiEndpoint* endp = EndpointAt(t); 605 606 printf("\tendpoint %ld (%p):\n", t, endp); 607 608 printf( 609 "\t\tid %ld, name '%s', %s, %s, %s, %s, refcount %ld\n", 610 endp->ID(), endp->Name(), 611 endp->IsConsumer() ? "consumer" : "producer", 612 endp->IsRegistered() ? "registered" : "unregistered", 613 endp->IsLocal() ? "local" : "remote", 614 endp->IsValid() ? "valid" : "invalid", endp->fRefCount); 615 616 printf("\t\tproperties: "); 617 endp->fProperties->PrintToStream(); 618 619 if (endp->IsConsumer()) { 620 BMidiConsumer* cons = (BMidiConsumer*) endp; 621 printf("\t\tport %ld, latency %Ld\n", 622 cons->fPort, cons->fLatency); 623 } else { 624 BMidiProducer* prod = (BMidiProducer*) endp; 625 if (prod->LockProducer()) { 626 printf("\t\tconnections:\n"); 627 for (int32 k = 0; k < prod->CountConsumers(); ++k) { 628 BMidiConsumer* cons = prod->ConsumerAt(k); 629 printf("\t\t\tid %ld (%p)\n", cons->ID(), cons); 630 } 631 prod->UnlockProducer(); 632 } 633 } 634 } 635 636 printf("*** END DumpEndpoints\n"); 637 Unlock(); 638 } 639 } 640 #endif 641 642