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