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 <Alert.h> 24 25 #include "debug.h" 26 #include "MidiServerApp.h" 27 #include "PortDrivers.h" 28 #include "ServerDefs.h" 29 #include "protocol.h" 30 31 //------------------------------------------------------------------------------ 32 33 MidiServerApp::MidiServerApp() 34 : BApplication(MIDI_SERVER_SIGNATURE) 35 { 36 TRACE(("Running OpenBeOS MIDI server")) 37 38 nextId = 1; 39 devWatcher.Start(); 40 } 41 42 //------------------------------------------------------------------------------ 43 44 MidiServerApp::~MidiServerApp() 45 { 46 for (int32 t = 0; t < CountApps(); ++t) 47 { 48 delete AppAt(t); 49 } 50 51 for (int32 t = 0; t < CountEndpoints(); ++t) 52 { 53 delete EndpointAt(t); 54 } 55 } 56 57 //------------------------------------------------------------------------------ 58 59 void MidiServerApp::AboutRequested() 60 { 61 (new BAlert(0, 62 "-write something funny here-", 63 "Okay", 0, 0, B_WIDTH_AS_USUAL, 64 B_INFO_ALERT))->Go(); 65 } 66 67 //------------------------------------------------------------------------------ 68 69 void MidiServerApp::MessageReceived(BMessage* msg) 70 { 71 #ifdef DEBUG 72 printf("IN "); msg->PrintToStream(); 73 #endif 74 75 // About thread safety inside the midi_server: even though 76 // multiple apps may be sending requests to the server at 77 // the same time, the BLooper's thread handles passes them 78 // to our MessageReceived() one after the other, so there 79 // is no need to synchronize anything. 80 81 switch (msg->what) 82 { 83 case MSG_REGISTER_APP: OnRegisterApp(msg); break; 84 case MSG_CREATE_ENDPOINT: OnCreateEndpoint(msg); break; 85 case MSG_DELETE_ENDPOINT: OnDeleteEndpoint(msg); break; 86 case MSG_PURGE_ENDPOINT: OnPurgeEndpoint(msg); break; 87 case MSG_CHANGE_ENDPOINT: OnChangeEndpoint(msg); break; 88 case MSG_CONNECT_ENDPOINTS: OnConnectDisconnect(msg); break; 89 case MSG_DISCONNECT_ENDPOINTS: OnConnectDisconnect(msg); break; 90 91 default: super::MessageReceived(msg); break; 92 } 93 } 94 95 //------------------------------------------------------------------------------ 96 97 void MidiServerApp::OnRegisterApp(BMessage* msg) 98 { 99 TRACE(("MidiServerApp::OnRegisterApp")) 100 101 // We only send the "app registered" message upon success, 102 // so if anything goes wrong here, we do not let the app 103 // know about it, and we consider it unregistered. (Most 104 // likely, the app is dead. If not, it freezes forever 105 // in anticipation of a message that will never arrive.) 106 107 app_t* app = new app_t; 108 109 if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK) 110 { 111 if (SendAllEndpoints(app)) 112 { 113 if (SendAllConnections(app)) 114 { 115 BMessage reply; 116 reply.what = MSG_APP_REGISTERED; 117 118 if (SendNotification(app, &reply)) 119 { 120 apps.AddItem(app); 121 122 #ifdef DEBUG 123 DumpApps(); 124 #endif 125 126 return; 127 } 128 } 129 } 130 } 131 132 delete app; 133 } 134 135 //------------------------------------------------------------------------------ 136 137 void MidiServerApp::OnCreateEndpoint(BMessage* msg) 138 { 139 TRACE(("MidiServerApp::OnCreateEndpoint")) 140 141 status_t err; 142 143 endpoint_t* endp = new endpoint_t; 144 145 endp->app = WhichApp(msg); 146 if (endp->app == NULL) 147 { 148 err = B_ERROR; 149 } 150 else 151 { 152 err = B_BAD_VALUE; 153 154 if ((msg->FindBool("midi:consumer", &endp->consumer) == B_OK) 155 && (msg->FindBool("midi:registered", &endp->registered) == B_OK) 156 && (msg->FindString("midi:name", &endp->name) == B_OK) 157 && (msg->FindMessage("midi:properties", &endp->properties) == B_OK)) 158 { 159 if (endp->consumer) 160 { 161 if ((msg->FindInt32("midi:port", &endp->port) == B_OK) 162 && (msg->FindInt64("midi:latency", &endp->latency) == B_OK)) 163 { 164 err = B_OK; 165 } 166 } 167 else 168 { 169 err = B_OK; 170 } 171 } 172 } 173 174 BMessage reply; 175 176 if (err == B_OK) 177 { 178 endp->id = nextId++; 179 reply.AddInt32("midi:id", endp->id); 180 } 181 182 reply.AddInt32("midi:result", err); 183 184 if (SendReply(endp->app, msg, &reply) && (err == B_OK)) 185 { 186 AddEndpoint(msg, endp); 187 } 188 else 189 { 190 delete endp; 191 } 192 } 193 194 //------------------------------------------------------------------------------ 195 196 void MidiServerApp::OnDeleteEndpoint(BMessage* msg) 197 { 198 TRACE(("MidiServerApp::OnDeleteEndpoint")) 199 200 // Clients send the "delete endpoint" message from 201 // the BMidiEndpoint destructor, so there is no point 202 // sending a reply, because the endpoint object will 203 // be destroyed no matter what. 204 205 app_t* app = WhichApp(msg); 206 if (app != NULL) 207 { 208 endpoint_t* endp = WhichEndpoint(msg, app); 209 if (endp != NULL) 210 { 211 RemoveEndpoint(app, endp); 212 } 213 } 214 } 215 216 //------------------------------------------------------------------------------ 217 218 void MidiServerApp::OnPurgeEndpoint(BMessage* msg) 219 { 220 TRACE(("MidiServerApp::OnPurgeEndpoint")) 221 222 // This performs the same task as OnDeleteEndpoint(), 223 // except that this message was send by the midi_server 224 // itself, so we don't check that the app that made the 225 // request really is the owner of the endpoint. (But we 226 // _do_ check that the message came from the server.) 227 228 if (!msg->IsSourceRemote()) 229 { 230 int32 id; 231 if (msg->FindInt32("midi:id", &id) == B_OK) 232 { 233 endpoint_t* endp = FindEndpoint(id); 234 if (endp != NULL) 235 { 236 RemoveEndpoint(NULL, endp); 237 } 238 } 239 } 240 } 241 242 //------------------------------------------------------------------------------ 243 244 void MidiServerApp::OnChangeEndpoint(BMessage* msg) 245 { 246 TRACE(("MidiServerApp::OnChangeEndpoint")) 247 248 endpoint_t* endp; 249 status_t err; 250 251 app_t* app = WhichApp(msg); 252 if (app == NULL) 253 { 254 err = B_ERROR; 255 } 256 else 257 { 258 endp = WhichEndpoint(msg, app); 259 if (endp == NULL) 260 { 261 err = B_BAD_VALUE; 262 } 263 else 264 { 265 err = B_OK; 266 } 267 } 268 269 BMessage reply; 270 reply.AddInt32("midi:result", err); 271 272 if (SendReply(app, msg, &reply) && (err == B_OK)) 273 { 274 TRACE(("Endpoint %ld (%p) changed", endp->id, endp)) 275 276 BMessage notify; 277 notify.what = MSG_ENDPOINT_CHANGED; 278 notify.AddInt32("midi:id", endp->id); 279 280 bool registered; 281 if (msg->FindBool("midi:registered", ®istered) == B_OK) 282 { 283 notify.AddBool("midi:registered", registered); 284 endp->registered = registered; 285 } 286 287 BString name; 288 if (msg->FindString("midi:name", &name) == B_OK) 289 { 290 notify.AddString("midi:name", name); 291 endp->name = name; 292 } 293 294 BMessage properties; 295 if (msg->FindMessage("midi:properties", &properties) == B_OK) 296 { 297 notify.AddMessage("midi:properties", &properties); 298 endp->properties = properties; 299 } 300 301 bigtime_t latency; 302 if (msg->FindInt64("midi:latency", &latency) == B_OK) 303 { 304 notify.AddInt64("midi:latency", latency); 305 endp->latency = latency; 306 } 307 308 NotifyAll(¬ify, app); 309 310 #ifdef DEBUG 311 DumpEndpoints(); 312 #endif 313 } 314 } 315 316 //------------------------------------------------------------------------------ 317 318 void MidiServerApp::OnConnectDisconnect(BMessage* msg) 319 { 320 TRACE(("MidiServerApp::OnConnectDisconnect")) 321 322 bool mustConnect = (msg->what == MSG_CONNECT_ENDPOINTS); 323 324 status_t err; 325 endpoint_t* prod; 326 endpoint_t* cons; 327 328 app_t* app = WhichApp(msg); 329 if (app == NULL) 330 { 331 err = B_ERROR; 332 } 333 else 334 { 335 err = B_BAD_VALUE; 336 337 int32 prodId, consId; 338 if ((msg->FindInt32("midi:producer", &prodId) == B_OK) 339 && (msg->FindInt32("midi:consumer", &consId) == B_OK)) 340 { 341 prod = FindEndpoint(prodId); 342 cons = FindEndpoint(consId); 343 344 if ((prod != NULL) && !prod->consumer) 345 { 346 if ((cons != NULL) && cons->consumer) 347 { 348 // It is an error to connect two endpoints that 349 // are already connected, or to disconnect two 350 // endpoints that are not connected at all. 351 352 if (mustConnect == prod->connections.HasItem(cons)) 353 { 354 err = B_ERROR; 355 } 356 else 357 { 358 err = B_OK; 359 } 360 } 361 } 362 } 363 } 364 365 BMessage reply; 366 reply.AddInt32("midi:result", err); 367 368 if (SendReply(app, msg, &reply) && (err == B_OK)) 369 { 370 if (mustConnect) 371 { 372 TRACE(("Connection made: %ld ---> %ld", prod->id, cons->id)) 373 374 prod->connections.AddItem(cons); 375 } 376 else 377 { 378 TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id)) 379 380 prod->connections.RemoveItem(cons); 381 } 382 383 BMessage notify; 384 MakeConnectedNotification(¬ify, prod, cons, mustConnect); 385 NotifyAll(¬ify, app); 386 387 #ifdef DEBUG 388 DumpEndpoints(); 389 #endif 390 } 391 } 392 393 //------------------------------------------------------------------------------ 394 395 bool MidiServerApp::SendAllEndpoints(app_t* app) 396 { 397 ASSERT(app != NULL) 398 399 BMessage notify; 400 401 for (int32 t = 0; t < CountEndpoints(); ++t) 402 { 403 endpoint_t* endp = EndpointAt(t); 404 405 MakeCreatedNotification(¬ify, endp); 406 407 if (!SendNotification(app, ¬ify)) { return false; } 408 } 409 410 return true; 411 } 412 413 //------------------------------------------------------------------------------ 414 415 bool MidiServerApp::SendAllConnections(app_t* app) 416 { 417 ASSERT(app != NULL) 418 419 BMessage notify; 420 421 for (int32 t = 0; t < CountEndpoints(); ++t) 422 { 423 endpoint_t* prod = EndpointAt(t); 424 if (!prod->consumer) 425 { 426 for (int32 k = 0; k < CountConnections(prod); ++k) 427 { 428 endpoint_t* cons = ConnectionAt(prod, k); 429 430 MakeConnectedNotification(¬ify, prod, cons, true); 431 432 if (!SendNotification(app, ¬ify)) { return false; } 433 } 434 } 435 } 436 437 return true; 438 } 439 440 //------------------------------------------------------------------------------ 441 442 void MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp) 443 { 444 ASSERT(msg != NULL) 445 ASSERT(endp != NULL) 446 ASSERT(!endpoints.HasItem(endp)) 447 448 TRACE(("Endpoint %ld (%p) added", endp->id, endp)) 449 450 endpoints.AddItem(endp); 451 452 BMessage notify; 453 MakeCreatedNotification(¬ify, endp); 454 NotifyAll(¬ify, endp->app); 455 456 #ifdef DEBUG 457 DumpEndpoints(); 458 #endif 459 } 460 461 //------------------------------------------------------------------------------ 462 463 void MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp) 464 { 465 ASSERT(endp != NULL) 466 ASSERT(endpoints.HasItem(endp)) 467 468 TRACE(("Endpoint %ld (%p) removed", endp->id, endp)) 469 470 endpoints.RemoveItem(endp); 471 472 if (endp->consumer) 473 { 474 DisconnectDeadConsumer(endp); 475 } 476 477 BMessage notify; 478 notify.what = MSG_ENDPOINT_DELETED; 479 notify.AddInt32("midi:id", endp->id); 480 NotifyAll(¬ify, app); 481 482 delete endp; 483 484 #ifdef DEBUG 485 DumpEndpoints(); 486 #endif 487 } 488 489 //------------------------------------------------------------------------------ 490 491 void MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons) 492 { 493 ASSERT(cons != NULL) 494 ASSERT(cons->consumer) 495 496 for (int32 t = 0; t < CountEndpoints(); ++t) 497 { 498 endpoint_t* prod = EndpointAt(t); 499 if (!prod->consumer) 500 { 501 prod->connections.RemoveItem(cons); 502 } 503 } 504 } 505 506 //------------------------------------------------------------------------------ 507 508 void MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp) 509 { 510 ASSERT(msg != NULL) 511 ASSERT(endp != NULL) 512 513 msg->MakeEmpty(); 514 msg->what = MSG_ENDPOINT_CREATED; 515 msg->AddInt32("midi:id", endp->id); 516 msg->AddBool("midi:consumer", endp->consumer); 517 msg->AddBool("midi:registered", endp->registered); 518 msg->AddString("midi:name", endp->name); 519 msg->AddMessage("midi:properties", &endp->properties); 520 521 if (endp->consumer) 522 { 523 msg->AddInt32("midi:port", endp->port); 524 msg->AddInt64("midi:latency", endp->latency); 525 } 526 } 527 528 //------------------------------------------------------------------------------ 529 530 void MidiServerApp::MakeConnectedNotification( 531 BMessage* msg, endpoint_t* prod, endpoint_t* cons, bool mustConnect) 532 { 533 ASSERT(msg != NULL) 534 ASSERT(prod != NULL) 535 ASSERT(cons != NULL) 536 ASSERT(!prod->consumer) 537 ASSERT(cons->consumer) 538 539 msg->MakeEmpty(); 540 541 if (mustConnect) 542 { 543 msg->what = MSG_ENDPOINTS_CONNECTED; 544 } 545 else 546 { 547 msg->what = MSG_ENDPOINTS_DISCONNECTED; 548 } 549 550 msg->AddInt32("midi:producer", prod->id); 551 msg->AddInt32("midi:consumer", cons->id); 552 } 553 554 //------------------------------------------------------------------------------ 555 556 app_t* MidiServerApp::WhichApp(BMessage* msg) 557 { 558 ASSERT(msg != NULL) 559 560 BMessenger retadr = msg->ReturnAddress(); 561 562 for (int32 t = 0; t < CountApps(); ++t) 563 { 564 app_t* app = AppAt(t); 565 if (app->messenger.Team() == retadr.Team()) 566 { 567 return app; 568 } 569 } 570 571 TRACE(("Application %ld is not registered", retadr.Team())) 572 573 return NULL; 574 } 575 576 //------------------------------------------------------------------------------ 577 578 endpoint_t* MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app) 579 { 580 ASSERT(msg != NULL) 581 ASSERT(app != NULL) 582 583 int32 id; 584 if (msg->FindInt32("midi:id", &id) == B_OK) 585 { 586 endpoint_t* endp = FindEndpoint(id); 587 if ((endp != NULL) && (endp->app == app)) 588 { 589 return endp; 590 } 591 } 592 593 TRACE(("Endpoint not found or wrong app")) 594 595 return NULL; 596 } 597 598 //------------------------------------------------------------------------------ 599 600 endpoint_t* MidiServerApp::FindEndpoint(int32 id) 601 { 602 if (id > 0) 603 { 604 for (int32 t = 0; t < CountEndpoints(); ++t) 605 { 606 endpoint_t* endp = EndpointAt(t); 607 if (endp->id == id) 608 { 609 return endp; 610 } 611 } 612 } 613 614 TRACE(("Endpoint %ld not found", id)) 615 616 return NULL; 617 } 618 619 //------------------------------------------------------------------------------ 620 621 void MidiServerApp::NotifyAll(BMessage* msg, app_t* except) 622 { 623 ASSERT(msg != NULL) 624 625 for (int32 t = CountApps() - 1; t >= 0; --t) 626 { 627 app_t* app = AppAt(t); 628 if (app != except) 629 { 630 if (!SendNotification(app, msg)) 631 { 632 delete (app_t*) apps.RemoveItem(t); 633 634 #ifdef DEBUG 635 DumpApps(); 636 #endif 637 } 638 } 639 } 640 } 641 642 //------------------------------------------------------------------------------ 643 644 bool MidiServerApp::SendNotification(app_t* app, BMessage* msg) 645 { 646 ASSERT(app != NULL) 647 ASSERT(msg != NULL) 648 649 status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT); 650 651 if (err != B_OK) 652 { 653 DeliveryError(app); 654 } 655 656 return (err == B_OK); 657 } 658 659 //------------------------------------------------------------------------------ 660 661 bool MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply) 662 { 663 ASSERT(msg != NULL) 664 ASSERT(reply != NULL) 665 666 status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT); 667 668 if ((err != B_OK) && (app != NULL)) 669 { 670 DeliveryError(app); 671 apps.RemoveItem(app); 672 delete app; 673 674 #ifdef DEBUG 675 DumpApps(); 676 #endif 677 } 678 679 return (err == B_OK); 680 } 681 682 //------------------------------------------------------------------------------ 683 684 void MidiServerApp::DeliveryError(app_t* app) 685 { 686 ASSERT(app != NULL) 687 688 // We cannot communicate with the app, so we assume it's 689 // dead. We need to remove its endpoints from the roster, 690 // but we cannot do that right away; removing endpoints 691 // triggers a bunch of new notifications and we don't want 692 // those to get in the way of the notifications we are 693 // currently sending out. Instead, we consider the death 694 // of an app as a separate event, and pretend that the 695 // now-dead app sent us delete requests for its endpoints. 696 697 TRACE(("Delivery error; unregistering app (%p)", app)) 698 699 BMessage msg; 700 701 for (int32 t = 0; t < CountEndpoints(); ++t) 702 { 703 endpoint_t* endp = EndpointAt(t); 704 if (endp->app == app) 705 { 706 msg.MakeEmpty(); 707 msg.what = MSG_PURGE_ENDPOINT; 708 msg.AddInt32("midi:id", endp->id); 709 710 // It is not safe to post a message to your own 711 // looper's message queue, because you risk a 712 // deadlock if the queue is full. The chance of 713 // that happening is fairly small, but just in 714 // case, we catch it with a timeout. Because this 715 // situation is so unlikely, I decided to simply 716 // forget about the whole "purge" message then. 717 718 if (be_app_messenger.SendMessage( 719 &msg, (BHandler*) NULL, TIMEOUT) != B_OK) 720 { 721 WARN("Could not deliver purge message") 722 } 723 } 724 } 725 } 726 727 //------------------------------------------------------------------------------ 728 729 int32 MidiServerApp::CountApps() 730 { 731 return apps.CountItems(); 732 } 733 734 //------------------------------------------------------------------------------ 735 736 app_t* MidiServerApp::AppAt(int32 index) 737 { 738 ASSERT(index >= 0 && index < CountApps()) 739 740 return (app_t*) apps.ItemAt(index); 741 } 742 743 //------------------------------------------------------------------------------ 744 745 int32 MidiServerApp::CountEndpoints() 746 { 747 return endpoints.CountItems(); 748 } 749 750 //------------------------------------------------------------------------------ 751 752 endpoint_t* MidiServerApp::EndpointAt(int32 index) 753 { 754 ASSERT(index >= 0 && index < CountEndpoints()) 755 756 return (endpoint_t*) endpoints.ItemAt(index); 757 } 758 759 //------------------------------------------------------------------------------ 760 761 int32 MidiServerApp::CountConnections(endpoint_t* prod) 762 { 763 ASSERT(prod != NULL) 764 ASSERT(!prod->consumer) 765 766 return prod->connections.CountItems(); 767 } 768 769 //------------------------------------------------------------------------------ 770 771 endpoint_t* MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index) 772 { 773 ASSERT(prod != NULL) 774 ASSERT(!prod->consumer) 775 ASSERT(index >= 0 && index < CountConnections(prod)) 776 777 return (endpoint_t*) prod->connections.ItemAt(index); 778 } 779 780 //------------------------------------------------------------------------------ 781 782 #ifdef DEBUG 783 void MidiServerApp::DumpApps() 784 { 785 printf("*** START DumpApps\n"); 786 787 for (int32 t = 0; t < CountApps(); ++t) 788 { 789 app_t* app = AppAt(t); 790 791 printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team()); 792 } 793 794 printf("*** END DumpApps\n"); 795 } 796 #endif 797 798 //------------------------------------------------------------------------------ 799 800 #ifdef DEBUG 801 void MidiServerApp::DumpEndpoints() 802 { 803 printf("*** START DumpEndpoints\n"); 804 805 for (int32 t = 0; t < CountEndpoints(); ++t) 806 { 807 endpoint_t* endp = EndpointAt(t); 808 809 printf("\tendpoint %ld (%p):\n", t, endp); 810 811 printf("\t\tid %ld, name '%s', %s, %s, app %p\n", 812 endp->id, endp->name.String(), 813 endp->consumer ? "consumer" : "producer", 814 endp->registered ? "registered" : "unregistered", 815 endp->app); 816 817 printf("\t\tproperties: "); endp->properties.PrintToStream(); 818 819 if (endp->consumer) 820 { 821 printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency); 822 } 823 else 824 { 825 printf("\t\tconnections:\n"); 826 for (int32 k = 0; k < CountConnections(endp); ++k) 827 { 828 endpoint_t* cons = ConnectionAt(endp, k); 829 printf("\t\t\tid %ld (%p)\n", cons->id, cons); 830 } 831 } 832 } 833 834 printf("*** END DumpEndpoints\n"); 835 } 836 #endif 837 838 //------------------------------------------------------------------------------ 839 840 int main() 841 { 842 MidiServerApp app; 843 app.Run(); 844 return 0; 845 } 846 847 //------------------------------------------------------------------------------ 848