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