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