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