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