headers/bsd: Add sys/queue.h.
[haiku.git] / src / servers / midi / MidiServerApp.cpp
blobccf1eab2b6570ca4aa947d3b3c816096288e7743
1 /*
2 * Copyright 2002-2015, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Matthijs Hollemans
7 */
9 /*
10 * Copyright (c) 2002-2004 Matthijs Hollemans
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 * DEALINGS IN THE SOFTWARE.
32 #include "MidiServerApp.h"
34 #include <new>
36 #include <Alert.h>
38 #include "debug.h"
39 #include "protocol.h"
40 #include "PortDrivers.h"
41 #include "ServerDefs.h"
44 using std::nothrow;
47 MidiServerApp::MidiServerApp(status_t& error)
49 BServer(MIDI_SERVER_SIGNATURE, true, &error)
51 TRACE(("Running Haiku MIDI server"))
53 fNextID = 1;
54 fDeviceWatcher = new(std::nothrow) DeviceWatcher();
55 if (fDeviceWatcher != NULL)
56 fDeviceWatcher->Run();
60 MidiServerApp::~MidiServerApp()
62 if (fDeviceWatcher && fDeviceWatcher->Lock())
63 fDeviceWatcher->Quit();
65 for (int32 t = 0; t < _CountApps(); ++t) {
66 delete _AppAt(t);
69 for (int32 t = 0; t < _CountEndpoints(); ++t) {
70 delete _EndpointAt(t);
75 void
76 MidiServerApp::AboutRequested()
78 BAlert* alert = new BAlert(0,
79 "Haiku midi_server 1.0.0 alpha\n\n"
80 "notes disguised as bytes\n"
81 "propagating to endpoints,\n"
82 "an aural delight",
83 "OK", 0, 0, B_WIDTH_AS_USUAL,
84 B_INFO_ALERT);
85 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
86 alert->Go();
90 void
91 MidiServerApp::MessageReceived(BMessage* msg)
93 #ifdef DEBUG
94 printf("IN "); msg->PrintToStream();
95 #endif
97 switch (msg->what) {
98 case MSG_REGISTER_APP:
99 _OnRegisterApp(msg);
100 break;
101 case MSG_CREATE_ENDPOINT:
102 _OnCreateEndpoint(msg);
103 break;
104 case MSG_DELETE_ENDPOINT:
105 _OnDeleteEndpoint(msg);
106 break;
107 case MSG_PURGE_ENDPOINT:
108 _OnPurgeEndpoint(msg);
109 break;
110 case MSG_CHANGE_ENDPOINT:
111 _OnChangeEndpoint(msg);
112 break;
113 case MSG_CONNECT_ENDPOINTS:
114 _OnConnectDisconnect(msg);
115 break;
116 case MSG_DISCONNECT_ENDPOINTS:
117 _OnConnectDisconnect(msg);
118 break;
120 default:
121 super::MessageReceived(msg);
122 break;
127 void
128 MidiServerApp::_OnRegisterApp(BMessage* msg)
130 TRACE(("MidiServerApp::_OnRegisterApp"))
132 // We only send the "app registered" message upon success,
133 // so if anything goes wrong here, we do not let the app
134 // know about it, and we consider it unregistered. (Most
135 // likely, the app is dead. If not, it freezes forever
136 // in anticipation of a message that will never arrive.)
138 app_t* app = new app_t;
140 if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK
141 && _SendAllEndpoints(app)
142 && _SendAllConnections(app)) {
143 BMessage reply;
144 reply.what = MSG_APP_REGISTERED;
146 if (_SendNotification(app, &reply)) {
147 fApps.AddItem(app);
148 #ifdef DEBUG
149 _DumpApps();
150 #endif
151 return;
155 delete app;
159 void
160 MidiServerApp::_OnCreateEndpoint(BMessage* msg)
162 TRACE(("MidiServerApp::_OnCreateEndpoint"))
164 status_t status;
165 endpoint_t* endpoint = new endpoint_t;
167 endpoint->app = _WhichApp(msg);
168 if (endpoint->app == NULL) {
169 status = B_ERROR;
170 } else {
171 status = B_BAD_VALUE;
173 if (msg->FindBool("midi:consumer", &endpoint->consumer) == B_OK
174 && msg->FindBool("midi:registered", &endpoint->registered) == B_OK
175 && msg->FindString("midi:name", &endpoint->name) == B_OK
176 && msg->FindMessage("midi:properties", &endpoint->properties)
177 == B_OK) {
178 if (endpoint->consumer) {
179 if (msg->FindInt32("midi:port", &endpoint->port) == B_OK
180 && msg->FindInt64("midi:latency", &endpoint->latency)
181 == B_OK)
182 status = B_OK;
183 } else
184 status = B_OK;
188 BMessage reply;
190 if (status == B_OK) {
191 endpoint->id = fNextID++;
192 reply.AddInt32("midi:id", endpoint->id);
195 reply.AddInt32("midi:result", status);
197 if (_SendReply(endpoint->app, msg, &reply) && status == B_OK)
198 _AddEndpoint(msg, endpoint);
199 else
200 delete endpoint;
204 void
205 MidiServerApp::_OnDeleteEndpoint(BMessage* msg)
207 TRACE(("MidiServerApp::_OnDeleteEndpoint"))
209 // Clients send the "delete endpoint" message from
210 // the BMidiEndpoint destructor, so there is no point
211 // sending a reply, because the endpoint object will
212 // be destroyed no matter what.
214 app_t* app = _WhichApp(msg);
215 if (app != NULL) {
216 endpoint_t* endpoint = _WhichEndpoint(msg, app);
217 if (endpoint != NULL)
218 _RemoveEndpoint(app, endpoint);
223 void
224 MidiServerApp::_OnPurgeEndpoint(BMessage* msg)
226 TRACE(("MidiServerApp::_OnPurgeEndpoint"))
228 // This performs the same task as OnDeleteEndpoint(),
229 // except that this message was send by the midi_server
230 // itself, so we don't check that the app that made the
231 // request really is the owner of the endpoint. (But we
232 // _do_ check that the message came from the server.)
234 if (!msg->IsSourceRemote()) {
235 int32 id;
236 if (msg->FindInt32("midi:id", &id) == B_OK) {
237 endpoint_t* endpoint = _FindEndpoint(id);
238 if (endpoint != NULL)
239 _RemoveEndpoint(NULL, endpoint);
245 void
246 MidiServerApp::_OnChangeEndpoint(BMessage* msg)
248 TRACE(("MidiServerApp::_OnChangeEndpoint"))
250 endpoint_t* endpoint = NULL;
251 status_t status;
253 app_t* app = _WhichApp(msg);
254 if (app == NULL)
255 status = B_ERROR;
256 else {
257 endpoint = _WhichEndpoint(msg, app);
258 if (endpoint == NULL)
259 status = B_BAD_VALUE;
260 else
261 status = B_OK;
264 BMessage reply;
265 reply.AddInt32("midi:result", status);
267 if (_SendReply(app, msg, &reply) && status == B_OK) {
268 TRACE(("Endpoint %" B_PRId32 " (%p) changed", endpoint->id, endpoint))
270 BMessage notify;
271 notify.what = MSG_ENDPOINT_CHANGED;
272 notify.AddInt32("midi:id", endpoint->id);
274 bool registered;
275 if (msg->FindBool("midi:registered", &registered) == B_OK) {
276 notify.AddBool("midi:registered", registered);
277 endpoint->registered = registered;
280 BString name;
281 if (msg->FindString("midi:name", &name) == B_OK) {
282 notify.AddString("midi:name", name);
283 endpoint->name = name;
286 BMessage properties;
287 if (msg->FindMessage("midi:properties", &properties) == B_OK) {
288 notify.AddMessage("midi:properties", &properties);
289 endpoint->properties = properties;
292 bigtime_t latency;
293 if (msg->FindInt64("midi:latency", &latency) == B_OK) {
294 notify.AddInt64("midi:latency", latency);
295 endpoint->latency = latency;
298 _NotifyAll(&notify, app);
300 #ifdef DEBUG
301 _DumpEndpoints();
302 #endif
307 void
308 MidiServerApp::_OnConnectDisconnect(BMessage* msg)
310 TRACE(("MidiServerApp::_OnConnectDisconnect"))
312 bool mustConnect = msg->what == MSG_CONNECT_ENDPOINTS;
314 status_t status;
315 endpoint_t* producer = NULL;
316 endpoint_t* consumer = NULL;
318 app_t* app = _WhichApp(msg);
319 if (app == NULL)
320 status = B_ERROR;
321 else {
322 status = B_BAD_VALUE;
324 int32 producerID;
325 int32 consumerID;
326 if (msg->FindInt32("midi:producer", &producerID) == B_OK
327 && msg->FindInt32("midi:consumer", &consumerID) == B_OK) {
328 producer = _FindEndpoint(producerID);
329 consumer = _FindEndpoint(consumerID);
331 if (producer != NULL && !producer->consumer) {
332 if (consumer != NULL && consumer->consumer) {
333 // It is an error to connect two endpoints that
334 // are already connected, or to disconnect two
335 // endpoints that are not connected at all.
337 if (mustConnect == producer->connections.HasItem(consumer))
338 status = B_ERROR;
339 else
340 status = B_OK;
346 BMessage reply;
347 reply.AddInt32("midi:result", status);
349 if (_SendReply(app, msg, &reply) && status == B_OK) {
350 if (mustConnect) {
351 TRACE(("Connection made: %" B_PRId32 " ---> %" B_PRId32,
352 producer->id, consumer->id))
354 producer->connections.AddItem(consumer);
355 } else {
356 TRACE(("Connection broken: %" B_PRId32 " -X-> %" B_PRId32,
357 producer->id, consumer->id))
359 producer->connections.RemoveItem(consumer);
362 BMessage notify;
363 _MakeConnectedNotification(&notify, producer, consumer, mustConnect);
364 _NotifyAll(&notify, app);
366 #ifdef DEBUG
367 _DumpEndpoints();
368 #endif
373 /*! Sends an app MSG_ENDPOINT_CREATED notifications for
374 all current endpoints. Used when the app registers.
376 bool
377 MidiServerApp::_SendAllEndpoints(app_t* app)
379 ASSERT(app != NULL)
381 BMessage notify;
383 for (int32 t = 0; t < _CountEndpoints(); ++t) {
384 endpoint_t* endpoint = _EndpointAt(t);
386 _MakeCreatedNotification(&notify, endpoint);
388 if (!_SendNotification(app, &notify))
389 return false;
392 return true;
396 /*! Sends an app MSG_ENDPOINTS_CONNECTED notifications for
397 all current connections. Used when the app registers.
399 bool
400 MidiServerApp::_SendAllConnections(app_t* app)
402 ASSERT(app != NULL)
404 BMessage notify;
406 for (int32 t = 0; t < _CountEndpoints(); ++t) {
407 endpoint_t* producer = _EndpointAt(t);
408 if (!producer->consumer) {
409 for (int32 k = 0; k < _CountConnections(producer); ++k) {
410 endpoint_t* consumer = _ConnectionAt(producer, k);
412 _MakeConnectedNotification(&notify, producer, consumer, true);
414 if (!_SendNotification(app, &notify))
415 return false;
420 return true;
424 /*! Adds the specified endpoint to the roster, and notifies
425 all other applications about this event.
427 void
428 MidiServerApp::_AddEndpoint(BMessage* msg, endpoint_t* endpoint)
430 ASSERT(msg != NULL)
431 ASSERT(endpoint != NULL)
432 ASSERT(!fEndpoints.HasItem(endpoint))
434 TRACE(("Endpoint %" B_PRId32 " (%p) added", endpoint->id, endpoint))
436 fEndpoints.AddItem(endpoint);
438 BMessage notify;
439 _MakeCreatedNotification(&notify, endpoint);
440 _NotifyAll(&notify, endpoint->app);
442 #ifdef DEBUG
443 _DumpEndpoints();
444 #endif
448 /*! Removes an endpoint from the roster, and notifies all
449 other apps about this event. "app" is the application
450 that the endpoint belongs to; if it is NULL, the app
451 no longer exists and we're purging the endpoint.
453 void
454 MidiServerApp::_RemoveEndpoint(app_t* app, endpoint_t* endpoint)
456 ASSERT(endpoint != NULL)
457 ASSERT(fEndpoints.HasItem(endpoint))
459 TRACE(("Endpoint %" B_PRId32 " (%p) removed", endpoint->id, endpoint))
461 fEndpoints.RemoveItem(endpoint);
463 if (endpoint->consumer)
464 _DisconnectDeadConsumer(endpoint);
466 BMessage notify;
467 notify.what = MSG_ENDPOINT_DELETED;
468 notify.AddInt32("midi:id", endpoint->id);
469 _NotifyAll(&notify, app);
471 delete endpoint;
473 #ifdef DEBUG
474 _DumpEndpoints();
475 #endif
479 /*! Removes a consumer from the list of connections of
480 all the producers it is connected to, just before
481 we remove it from the roster.
483 void
484 MidiServerApp::_DisconnectDeadConsumer(endpoint_t* consumer)
486 ASSERT(consumer != NULL)
487 ASSERT(consumer->consumer)
489 for (int32 t = 0; t < _CountEndpoints(); ++t) {
490 endpoint_t* producer = _EndpointAt(t);
491 if (!producer->consumer)
492 producer->connections.RemoveItem(consumer);
497 //! Fills up a MSG_ENDPOINT_CREATED message.
498 void
499 MidiServerApp::_MakeCreatedNotification(BMessage* msg, endpoint_t* endpoint)
501 ASSERT(msg != NULL)
502 ASSERT(endpoint != NULL)
504 msg->MakeEmpty();
505 msg->what = MSG_ENDPOINT_CREATED;
506 msg->AddInt32("midi:id", endpoint->id);
507 msg->AddBool("midi:consumer", endpoint->consumer);
508 msg->AddBool("midi:registered", endpoint->registered);
509 msg->AddString("midi:name", endpoint->name);
510 msg->AddMessage("midi:properties", &endpoint->properties);
512 if (endpoint->consumer) {
513 msg->AddInt32("midi:port", endpoint->port);
514 msg->AddInt64("midi:latency", endpoint->latency);
519 //! Fills up a MSG_ENDPOINTS_(DIS)CONNECTED message.
520 void
521 MidiServerApp::_MakeConnectedNotification(BMessage* msg, endpoint_t* producer,
522 endpoint_t* consumer, bool mustConnect)
524 ASSERT(msg != NULL)
525 ASSERT(producer != NULL)
526 ASSERT(consumer != NULL)
527 ASSERT(!producer->consumer)
528 ASSERT(consumer->consumer)
530 msg->MakeEmpty();
532 if (mustConnect)
533 msg->what = MSG_ENDPOINTS_CONNECTED;
534 else
535 msg->what = MSG_ENDPOINTS_DISCONNECTED;
537 msg->AddInt32("midi:producer", producer->id);
538 msg->AddInt32("midi:consumer", consumer->id);
542 /*! Figures out which application a message came from.
543 Returns NULL if the application is not registered.
545 app_t*
546 MidiServerApp::_WhichApp(BMessage* msg)
548 ASSERT(msg != NULL)
550 BMessenger retadr = msg->ReturnAddress();
552 for (int32 t = 0; t < _CountApps(); ++t) {
553 app_t* app = _AppAt(t);
554 if (app->messenger.Team() == retadr.Team())
555 return app;
558 TRACE(("Application %" B_PRId32 " is not registered", retadr.Team()))
560 return NULL;
564 /*! Looks at the "midi:id" field from a message, and returns
565 the endpoint object that corresponds to that ID. It also
566 checks whether the application specified by "app" really
567 owns the endpoint. Returns NULL on error.
569 endpoint_t*
570 MidiServerApp::_WhichEndpoint(BMessage* msg, app_t* app)
572 ASSERT(msg != NULL)
573 ASSERT(app != NULL)
575 int32 id;
576 if (msg->FindInt32("midi:id", &id) == B_OK) {
577 endpoint_t* endpoint = _FindEndpoint(id);
578 if (endpoint != NULL && endpoint->app == app)
579 return endpoint;
582 TRACE(("Endpoint not found or wrong app"))
583 return NULL;
587 /*! Returns the endpoint with the specified ID, or
588 \c NULL if no such endpoint exists on the roster.
590 endpoint_t*
591 MidiServerApp::_FindEndpoint(int32 id)
593 if (id > 0) {
594 for (int32 t = 0; t < _CountEndpoints(); ++t) {
595 endpoint_t* endpoint = _EndpointAt(t);
596 if (endpoint->id == id)
597 return endpoint;
601 TRACE(("Endpoint %" B_PRId32 " not found", id))
602 return NULL;
606 /*! Sends notification messages to all registered apps,
607 except to the application that triggered the event.
608 The "except" app is allowed to be NULL.
610 void
611 MidiServerApp::_NotifyAll(BMessage* msg, app_t* except)
613 ASSERT(msg != NULL)
615 for (int32 t = _CountApps() - 1; t >= 0; --t) {
616 app_t* app = _AppAt(t);
617 if (app != except && !_SendNotification(app, msg)) {
618 delete (app_t*)fApps.RemoveItem(t);
619 #ifdef DEBUG
620 _DumpApps();
621 #endif
627 /*! Sends a notification message to an application, which is
628 not necessarily registered yet. Applications never reply
629 to such notification messages.
631 bool
632 MidiServerApp::_SendNotification(app_t* app, BMessage* msg)
634 ASSERT(app != NULL)
635 ASSERT(msg != NULL)
637 status_t status = app->messenger.SendMessage(msg, (BHandler*) NULL,
638 TIMEOUT);
639 if (status != B_OK)
640 _DeliveryError(app);
642 return status == B_OK;
646 /*! Sends a reply to a request made by an application.
647 If "app" is NULL, the application is not registered
648 (and the reply should contain an error code).
650 bool
651 MidiServerApp::_SendReply(app_t* app, BMessage* msg, BMessage* reply)
653 ASSERT(msg != NULL)
654 ASSERT(reply != NULL)
656 status_t status = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
657 if (status != B_OK && app != NULL) {
658 _DeliveryError(app);
659 fApps.RemoveItem(app);
660 delete app;
662 #ifdef DEBUG
663 _DumpApps();
664 #endif
667 return status == B_OK;
671 /*! Removes an app and all of its endpoints from the roster
672 if a reply or notification message cannot be delivered.
673 (Waiting for communications to fail is actually our only
674 way to get rid of stale endpoints.)
676 void
677 MidiServerApp::_DeliveryError(app_t* app)
679 ASSERT(app != NULL)
681 // We cannot communicate with the app, so we assume it's
682 // dead. We need to remove its endpoints from the roster,
683 // but we cannot do that right away; removing endpoints
684 // triggers a bunch of new notifications and we don't want
685 // those to get in the way of the notifications we are
686 // currently sending out. Instead, we consider the death
687 // of an app as a separate event, and pretend that the
688 // now-dead app sent us delete requests for its endpoints.
690 TRACE(("Delivery error; unregistering app (%p)", app))
692 BMessage msg;
694 for (int32 t = 0; t < _CountEndpoints(); ++t) {
695 endpoint_t* endpoint = _EndpointAt(t);
696 if (endpoint->app == app) {
697 msg.MakeEmpty();
698 msg.what = MSG_PURGE_ENDPOINT;
699 msg.AddInt32("midi:id", endpoint->id);
701 // It is not safe to post a message to your own
702 // looper's message queue, because you risk a
703 // deadlock if the queue is full. The chance of
704 // that happening is fairly small, but just in
705 // case, we catch it with a timeout. Because this
706 // situation is so unlikely, I decided to simply
707 // forget about the whole "purge" message then.
709 if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
710 TIMEOUT) != B_OK) {
711 WARN("Could not deliver purge message")
718 int32
719 MidiServerApp::_CountApps()
721 return fApps.CountItems();
725 app_t*
726 MidiServerApp::_AppAt(int32 index)
728 ASSERT(index >= 0 && index < _CountApps())
730 return (app_t*)fApps.ItemAt(index);
734 int32
735 MidiServerApp::_CountEndpoints()
737 return fEndpoints.CountItems();
741 endpoint_t*
742 MidiServerApp::_EndpointAt(int32 index)
744 ASSERT(index >= 0 && index < _CountEndpoints())
746 return (endpoint_t*)fEndpoints.ItemAt(index);
750 int32
751 MidiServerApp::_CountConnections(endpoint_t* producer)
753 ASSERT(producer != NULL)
754 ASSERT(!producer->consumer)
756 return producer->connections.CountItems();
760 endpoint_t*
761 MidiServerApp::_ConnectionAt(endpoint_t* producer, int32 index)
763 ASSERT(producer != NULL)
764 ASSERT(!producer->consumer)
765 ASSERT(index >= 0 && index < _CountConnections(producer))
767 return (endpoint_t*)producer->connections.ItemAt(index);
771 #ifdef DEBUG
772 void
773 MidiServerApp::_DumpApps()
775 printf("*** START DumpApps\n");
777 for (int32 t = 0; t < _CountApps(); ++t) {
778 app_t* app = _AppAt(t);
780 printf("\tapp %" B_PRId32 " (%p): team %" B_PRId32 "\n", t, app,
781 app->messenger.Team());
784 printf("*** END DumpApps\n");
788 void
789 MidiServerApp::_DumpEndpoints()
791 printf("*** START DumpEndpoints\n");
793 for (int32 t = 0; t < _CountEndpoints(); ++t) {
794 endpoint_t* endpoint = _EndpointAt(t);
796 printf("\tendpoint %" B_PRId32 " (%p):\n", t, endpoint);
797 printf("\t\tid %" B_PRId32 ", name '%s', %s, %s, app %p\n",
798 endpoint->id, endpoint->name.String(),
799 endpoint->consumer ? "consumer" : "producer",
800 endpoint->registered ? "registered" : "unregistered",
801 endpoint->app);
802 printf("\t\tproperties: "); endpoint->properties.PrintToStream();
804 if (endpoint->consumer)
805 printf("\t\tport %" B_PRId32 ", latency %" B_PRIdBIGTIME "\n",
806 endpoint->port, endpoint->latency);
807 else {
808 printf("\t\tconnections:\n");
809 for (int32 k = 0; k < _CountConnections(endpoint); ++k) {
810 endpoint_t* consumer = _ConnectionAt(endpoint, k);
811 printf("\t\t\tid %" B_PRId32 " (%p)\n", consumer->id, consumer);
816 printf("*** END DumpEndpoints\n");
818 #endif // DEBUG
821 // #pragma mark -
825 main()
827 status_t status;
828 MidiServerApp app(status);
830 if (status == B_OK)
831 app.Run();
833 return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;