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 %ld (%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: %ld ---> %ld", prod->id, cons->id)) 324 325 prod->connections.AddItem(cons); 326 } else { 327 TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id)) 328 329 prod->connections.RemoveItem(cons); 330 } 331 332 BMessage notify; 333 MakeConnectedNotification(¬ify, prod, cons, mustConnect); 334 NotifyAll(¬ify, app); 335 336 #ifdef DEBUG 337 DumpEndpoints(); 338 #endif 339 } 340 } 341 342 343 bool 344 MidiServerApp::SendAllEndpoints(app_t* app) 345 { 346 ASSERT(app != NULL) 347 348 BMessage notify; 349 350 for (int32 t = 0; t < CountEndpoints(); ++t) { 351 endpoint_t* endp = EndpointAt(t); 352 353 MakeCreatedNotification(¬ify, endp); 354 355 if (!SendNotification(app, ¬ify)) 356 return false; 357 } 358 359 return true; 360 } 361 362 363 bool 364 MidiServerApp::SendAllConnections(app_t* app) 365 { 366 ASSERT(app != NULL) 367 368 BMessage notify; 369 370 for (int32 t = 0; t < CountEndpoints(); ++t) { 371 endpoint_t* prod = EndpointAt(t); 372 if (!prod->consumer) { 373 for (int32 k = 0; k < CountConnections(prod); ++k) { 374 endpoint_t* cons = ConnectionAt(prod, k); 375 376 MakeConnectedNotification(¬ify, prod, cons, true); 377 378 if (!SendNotification(app, ¬ify)) 379 return false; 380 } 381 } 382 } 383 384 return true; 385 } 386 387 388 void 389 MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp) 390 { 391 ASSERT(msg != NULL) 392 ASSERT(endp != NULL) 393 ASSERT(!endpoints.HasItem(endp)) 394 395 TRACE(("Endpoint %ld (%p) added", endp->id, endp)) 396 397 endpoints.AddItem(endp); 398 399 BMessage notify; 400 MakeCreatedNotification(¬ify, endp); 401 NotifyAll(¬ify, endp->app); 402 403 #ifdef DEBUG 404 DumpEndpoints(); 405 #endif 406 } 407 408 409 void 410 MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp) 411 { 412 ASSERT(endp != NULL) 413 ASSERT(endpoints.HasItem(endp)) 414 415 TRACE(("Endpoint %ld (%p) removed", endp->id, endp)) 416 417 endpoints.RemoveItem(endp); 418 419 if (endp->consumer) 420 DisconnectDeadConsumer(endp); 421 422 BMessage notify; 423 notify.what = MSG_ENDPOINT_DELETED; 424 notify.AddInt32("midi:id", endp->id); 425 NotifyAll(¬ify, app); 426 427 delete endp; 428 429 #ifdef DEBUG 430 DumpEndpoints(); 431 #endif 432 } 433 434 435 void 436 MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons) 437 { 438 ASSERT(cons != NULL) 439 ASSERT(cons->consumer) 440 441 for (int32 t = 0; t < CountEndpoints(); ++t) { 442 endpoint_t* prod = EndpointAt(t); 443 if (!prod->consumer) 444 prod->connections.RemoveItem(cons); 445 } 446 } 447 448 449 void 450 MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp) 451 { 452 ASSERT(msg != NULL) 453 ASSERT(endp != NULL) 454 455 msg->MakeEmpty(); 456 msg->what = MSG_ENDPOINT_CREATED; 457 msg->AddInt32("midi:id", endp->id); 458 msg->AddBool("midi:consumer", endp->consumer); 459 msg->AddBool("midi:registered", endp->registered); 460 msg->AddString("midi:name", endp->name); 461 msg->AddMessage("midi:properties", &endp->properties); 462 463 if (endp->consumer) { 464 msg->AddInt32("midi:port", endp->port); 465 msg->AddInt64("midi:latency", endp->latency); 466 } 467 } 468 469 470 void 471 MidiServerApp::MakeConnectedNotification(BMessage* msg, endpoint_t* prod, 472 endpoint_t* cons, bool mustConnect) 473 { 474 ASSERT(msg != NULL) 475 ASSERT(prod != NULL) 476 ASSERT(cons != NULL) 477 ASSERT(!prod->consumer) 478 ASSERT(cons->consumer) 479 480 msg->MakeEmpty(); 481 482 if (mustConnect) 483 msg->what = MSG_ENDPOINTS_CONNECTED; 484 else 485 msg->what = MSG_ENDPOINTS_DISCONNECTED; 486 487 msg->AddInt32("midi:producer", prod->id); 488 msg->AddInt32("midi:consumer", cons->id); 489 } 490 491 492 app_t* 493 MidiServerApp::WhichApp(BMessage* msg) 494 { 495 ASSERT(msg != NULL) 496 497 BMessenger retadr = msg->ReturnAddress(); 498 499 for (int32 t = 0; t < CountApps(); ++t) { 500 app_t* app = AppAt(t); 501 if (app->messenger.Team() == retadr.Team()) 502 return app; 503 } 504 505 TRACE(("Application %ld is not registered", retadr.Team())) 506 507 return NULL; 508 } 509 510 511 endpoint_t* 512 MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app) 513 { 514 ASSERT(msg != NULL) 515 ASSERT(app != NULL) 516 517 int32 id; 518 if (msg->FindInt32("midi:id", &id) == B_OK) { 519 endpoint_t* endp = FindEndpoint(id); 520 if (endp != NULL && endp->app == app) 521 return endp; 522 } 523 524 TRACE(("Endpoint not found or wrong app")) 525 return NULL; 526 } 527 528 529 endpoint_t* 530 MidiServerApp::FindEndpoint(int32 id) 531 { 532 if (id > 0) { 533 for (int32 t = 0; t < CountEndpoints(); ++t) { 534 endpoint_t* endp = EndpointAt(t); 535 if (endp->id == id) 536 return endp; 537 } 538 } 539 540 TRACE(("Endpoint %ld not found", id)) 541 return NULL; 542 } 543 544 545 void 546 MidiServerApp::NotifyAll(BMessage* msg, app_t* except) 547 { 548 ASSERT(msg != NULL) 549 550 for (int32 t = CountApps() - 1; t >= 0; --t) { 551 app_t* app = AppAt(t); 552 if (app != except) { 553 if (!SendNotification(app, msg)) { 554 delete (app_t*)apps.RemoveItem(t); 555 556 #ifdef DEBUG 557 DumpApps(); 558 #endif 559 } 560 } 561 } 562 } 563 564 565 bool 566 MidiServerApp::SendNotification(app_t* app, BMessage* msg) 567 { 568 ASSERT(app != NULL) 569 ASSERT(msg != NULL) 570 571 status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT); 572 573 if (err != B_OK) 574 DeliveryError(app); 575 576 return err == B_OK; 577 } 578 579 580 bool 581 MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply) 582 { 583 ASSERT(msg != NULL) 584 ASSERT(reply != NULL) 585 586 status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT); 587 588 if (err != B_OK && app != NULL) { 589 DeliveryError(app); 590 apps.RemoveItem(app); 591 delete app; 592 593 #ifdef DEBUG 594 DumpApps(); 595 #endif 596 } 597 598 return err == B_OK; 599 } 600 601 602 void 603 MidiServerApp::DeliveryError(app_t* app) 604 { 605 ASSERT(app != NULL) 606 607 // We cannot communicate with the app, so we assume it's 608 // dead. We need to remove its endpoints from the roster, 609 // but we cannot do that right away; removing endpoints 610 // triggers a bunch of new notifications and we don't want 611 // those to get in the way of the notifications we are 612 // currently sending out. Instead, we consider the death 613 // of an app as a separate event, and pretend that the 614 // now-dead app sent us delete requests for its endpoints. 615 616 TRACE(("Delivery error; unregistering app (%p)", app)) 617 618 BMessage msg; 619 620 for (int32 t = 0; t < CountEndpoints(); ++t) { 621 endpoint_t* endp = EndpointAt(t); 622 if (endp->app == app) { 623 msg.MakeEmpty(); 624 msg.what = MSG_PURGE_ENDPOINT; 625 msg.AddInt32("midi:id", endp->id); 626 627 // It is not safe to post a message to your own 628 // looper's message queue, because you risk a 629 // deadlock if the queue is full. The chance of 630 // that happening is fairly small, but just in 631 // case, we catch it with a timeout. Because this 632 // situation is so unlikely, I decided to simply 633 // forget about the whole "purge" message then. 634 635 if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL, 636 TIMEOUT) != B_OK) { 637 WARN("Could not deliver purge message") 638 } 639 } 640 } 641 } 642 643 644 int32 645 MidiServerApp::CountApps() 646 { 647 return apps.CountItems(); 648 } 649 650 651 app_t* 652 MidiServerApp::AppAt(int32 index) 653 { 654 ASSERT(index >= 0 && index < CountApps()) 655 656 return (app_t*)apps.ItemAt(index); 657 } 658 659 660 int32 661 MidiServerApp::CountEndpoints() 662 { 663 return endpoints.CountItems(); 664 } 665 666 667 endpoint_t* 668 MidiServerApp::EndpointAt(int32 index) 669 { 670 ASSERT(index >= 0 && index < CountEndpoints()) 671 672 return (endpoint_t*)endpoints.ItemAt(index); 673 } 674 675 676 int32 677 MidiServerApp::CountConnections(endpoint_t* prod) 678 { 679 ASSERT(prod != NULL) 680 ASSERT(!prod->consumer) 681 682 return prod->connections.CountItems(); 683 } 684 685 686 endpoint_t* 687 MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index) 688 { 689 ASSERT(prod != NULL) 690 ASSERT(!prod->consumer) 691 ASSERT(index >= 0 && index < CountConnections(prod)) 692 693 return (endpoint_t*)prod->connections.ItemAt(index); 694 } 695 696 697 #ifdef DEBUG 698 void 699 MidiServerApp::DumpApps() 700 { 701 printf("*** START DumpApps\n"); 702 703 for (int32 t = 0; t < CountApps(); ++t) { 704 app_t* app = AppAt(t); 705 706 printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team()); 707 } 708 709 printf("*** END DumpApps\n"); 710 } 711 #endif 712 713 714 #ifdef DEBUG 715 void 716 MidiServerApp::DumpEndpoints() 717 { 718 printf("*** START DumpEndpoints\n"); 719 720 for (int32 t = 0; t < CountEndpoints(); ++t) { 721 endpoint_t* endp = EndpointAt(t); 722 723 printf("\tendpoint %ld (%p):\n", t, endp); 724 printf("\t\tid %ld, name '%s', %s, %s, app %p\n", 725 endp->id, endp->name.String(), 726 endp->consumer ? "consumer" : "producer", 727 endp->registered ? "registered" : "unregistered", 728 endp->app); 729 printf("\t\tproperties: "); endp->properties.PrintToStream(); 730 731 if (endp->consumer) 732 printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency); 733 else { 734 printf("\t\tconnections:\n"); 735 for (int32 k = 0; k < CountConnections(endp); ++k) { 736 endpoint_t* cons = ConnectionAt(endp, k); 737 printf("\t\t\tid %ld (%p)\n", cons->id, cons); 738 } 739 } 740 } 741 742 printf("*** END DumpEndpoints\n"); 743 } 744 #endif 745 746 747 // #pragma mark - 748 749 750 int 751 main() 752 { 753 MidiServerApp app; 754 app.Run(); 755 return 0; 756 } 757 758