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