2 * Copyright 2002-2015, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
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"
40 #include "PortDrivers.h"
41 #include "ServerDefs.h"
47 MidiServerApp::MidiServerApp(status_t
& error
)
49 BServer(MIDI_SERVER_SIGNATURE
, true, &error
)
51 TRACE(("Running Haiku MIDI server"))
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
) {
69 for (int32 t
= 0; t
< _CountEndpoints(); ++t
) {
70 delete _EndpointAt(t
);
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"
83 "OK", 0, 0, B_WIDTH_AS_USUAL
,
85 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
91 MidiServerApp::MessageReceived(BMessage
* msg
)
94 printf("IN "); msg
->PrintToStream();
98 case MSG_REGISTER_APP
:
101 case MSG_CREATE_ENDPOINT
:
102 _OnCreateEndpoint(msg
);
104 case MSG_DELETE_ENDPOINT
:
105 _OnDeleteEndpoint(msg
);
107 case MSG_PURGE_ENDPOINT
:
108 _OnPurgeEndpoint(msg
);
110 case MSG_CHANGE_ENDPOINT
:
111 _OnChangeEndpoint(msg
);
113 case MSG_CONNECT_ENDPOINTS
:
114 _OnConnectDisconnect(msg
);
116 case MSG_DISCONNECT_ENDPOINTS
:
117 _OnConnectDisconnect(msg
);
121 super::MessageReceived(msg
);
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
)) {
144 reply
.what
= MSG_APP_REGISTERED
;
146 if (_SendNotification(app
, &reply
)) {
160 MidiServerApp::_OnCreateEndpoint(BMessage
* msg
)
162 TRACE(("MidiServerApp::_OnCreateEndpoint"))
165 endpoint_t
* endpoint
= new endpoint_t
;
167 endpoint
->app
= _WhichApp(msg
);
168 if (endpoint
->app
== NULL
) {
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
)
178 if (endpoint
->consumer
) {
179 if (msg
->FindInt32("midi:port", &endpoint
->port
) == B_OK
180 && msg
->FindInt64("midi:latency", &endpoint
->latency
)
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
);
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
);
216 endpoint_t
* endpoint
= _WhichEndpoint(msg
, app
);
217 if (endpoint
!= NULL
)
218 _RemoveEndpoint(app
, endpoint
);
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()) {
236 if (msg
->FindInt32("midi:id", &id
) == B_OK
) {
237 endpoint_t
* endpoint
= _FindEndpoint(id
);
238 if (endpoint
!= NULL
)
239 _RemoveEndpoint(NULL
, endpoint
);
246 MidiServerApp::_OnChangeEndpoint(BMessage
* msg
)
248 TRACE(("MidiServerApp::_OnChangeEndpoint"))
250 endpoint_t
* endpoint
= NULL
;
253 app_t
* app
= _WhichApp(msg
);
257 endpoint
= _WhichEndpoint(msg
, app
);
258 if (endpoint
== NULL
)
259 status
= B_BAD_VALUE
;
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
))
271 notify
.what
= MSG_ENDPOINT_CHANGED
;
272 notify
.AddInt32("midi:id", endpoint
->id
);
275 if (msg
->FindBool("midi:registered", ®istered
) == B_OK
) {
276 notify
.AddBool("midi:registered", registered
);
277 endpoint
->registered
= registered
;
281 if (msg
->FindString("midi:name", &name
) == B_OK
) {
282 notify
.AddString("midi:name", name
);
283 endpoint
->name
= name
;
287 if (msg
->FindMessage("midi:properties", &properties
) == B_OK
) {
288 notify
.AddMessage("midi:properties", &properties
);
289 endpoint
->properties
= properties
;
293 if (msg
->FindInt64("midi:latency", &latency
) == B_OK
) {
294 notify
.AddInt64("midi:latency", latency
);
295 endpoint
->latency
= latency
;
298 _NotifyAll(¬ify
, app
);
308 MidiServerApp::_OnConnectDisconnect(BMessage
* msg
)
310 TRACE(("MidiServerApp::_OnConnectDisconnect"))
312 bool mustConnect
= msg
->what
== MSG_CONNECT_ENDPOINTS
;
315 endpoint_t
* producer
= NULL
;
316 endpoint_t
* consumer
= NULL
;
318 app_t
* app
= _WhichApp(msg
);
322 status
= B_BAD_VALUE
;
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
))
347 reply
.AddInt32("midi:result", status
);
349 if (_SendReply(app
, msg
, &reply
) && status
== B_OK
) {
351 TRACE(("Connection made: %" B_PRId32
" ---> %" B_PRId32
,
352 producer
->id
, consumer
->id
))
354 producer
->connections
.AddItem(consumer
);
356 TRACE(("Connection broken: %" B_PRId32
" -X-> %" B_PRId32
,
357 producer
->id
, consumer
->id
))
359 producer
->connections
.RemoveItem(consumer
);
363 _MakeConnectedNotification(¬ify
, producer
, consumer
, mustConnect
);
364 _NotifyAll(¬ify
, app
);
373 /*! Sends an app MSG_ENDPOINT_CREATED notifications for
374 all current endpoints. Used when the app registers.
377 MidiServerApp::_SendAllEndpoints(app_t
* app
)
383 for (int32 t
= 0; t
< _CountEndpoints(); ++t
) {
384 endpoint_t
* endpoint
= _EndpointAt(t
);
386 _MakeCreatedNotification(¬ify
, endpoint
);
388 if (!_SendNotification(app
, ¬ify
))
396 /*! Sends an app MSG_ENDPOINTS_CONNECTED notifications for
397 all current connections. Used when the app registers.
400 MidiServerApp::_SendAllConnections(app_t
* app
)
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(¬ify
, producer
, consumer
, true);
414 if (!_SendNotification(app
, ¬ify
))
424 /*! Adds the specified endpoint to the roster, and notifies
425 all other applications about this event.
428 MidiServerApp::_AddEndpoint(BMessage
* msg
, endpoint_t
* endpoint
)
431 ASSERT(endpoint
!= NULL
)
432 ASSERT(!fEndpoints
.HasItem(endpoint
))
434 TRACE(("Endpoint %" B_PRId32
" (%p) added", endpoint
->id
, endpoint
))
436 fEndpoints
.AddItem(endpoint
);
439 _MakeCreatedNotification(¬ify
, endpoint
);
440 _NotifyAll(¬ify
, endpoint
->app
);
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.
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
);
467 notify
.what
= MSG_ENDPOINT_DELETED
;
468 notify
.AddInt32("midi:id", endpoint
->id
);
469 _NotifyAll(¬ify
, app
);
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.
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.
499 MidiServerApp::_MakeCreatedNotification(BMessage
* msg
, endpoint_t
* endpoint
)
502 ASSERT(endpoint
!= NULL
)
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.
521 MidiServerApp::_MakeConnectedNotification(BMessage
* msg
, endpoint_t
* producer
,
522 endpoint_t
* consumer
, bool mustConnect
)
525 ASSERT(producer
!= NULL
)
526 ASSERT(consumer
!= NULL
)
527 ASSERT(!producer
->consumer
)
528 ASSERT(consumer
->consumer
)
533 msg
->what
= MSG_ENDPOINTS_CONNECTED
;
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.
546 MidiServerApp::_WhichApp(BMessage
* msg
)
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())
558 TRACE(("Application %" B_PRId32
" is not registered", retadr
.Team()))
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.
570 MidiServerApp::_WhichEndpoint(BMessage
* msg
, app_t
* app
)
576 if (msg
->FindInt32("midi:id", &id
) == B_OK
) {
577 endpoint_t
* endpoint
= _FindEndpoint(id
);
578 if (endpoint
!= NULL
&& endpoint
->app
== app
)
582 TRACE(("Endpoint not found or wrong app"))
587 /*! Returns the endpoint with the specified ID, or
588 \c NULL if no such endpoint exists on the roster.
591 MidiServerApp::_FindEndpoint(int32 id
)
594 for (int32 t
= 0; t
< _CountEndpoints(); ++t
) {
595 endpoint_t
* endpoint
= _EndpointAt(t
);
596 if (endpoint
->id
== id
)
601 TRACE(("Endpoint %" B_PRId32
" not found", id
))
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.
611 MidiServerApp::_NotifyAll(BMessage
* msg
, app_t
* except
)
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
);
627 /*! Sends a notification message to an application, which is
628 not necessarily registered yet. Applications never reply
629 to such notification messages.
632 MidiServerApp::_SendNotification(app_t
* app
, BMessage
* msg
)
637 status_t status
= app
->messenger
.SendMessage(msg
, (BHandler
*) NULL
,
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).
651 MidiServerApp::_SendReply(app_t
* app
, BMessage
* msg
, BMessage
* reply
)
654 ASSERT(reply
!= NULL
)
656 status_t status
= msg
->SendReply(reply
, (BHandler
*) NULL
, TIMEOUT
);
657 if (status
!= B_OK
&& app
!= NULL
) {
659 fApps
.RemoveItem(app
);
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.)
677 MidiServerApp::_DeliveryError(app_t
* app
)
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
))
694 for (int32 t
= 0; t
< _CountEndpoints(); ++t
) {
695 endpoint_t
* endpoint
= _EndpointAt(t
);
696 if (endpoint
->app
== app
) {
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
,
711 WARN("Could not deliver purge message")
719 MidiServerApp::_CountApps()
721 return fApps
.CountItems();
726 MidiServerApp::_AppAt(int32 index
)
728 ASSERT(index
>= 0 && index
< _CountApps())
730 return (app_t
*)fApps
.ItemAt(index
);
735 MidiServerApp::_CountEndpoints()
737 return fEndpoints
.CountItems();
742 MidiServerApp::_EndpointAt(int32 index
)
744 ASSERT(index
>= 0 && index
< _CountEndpoints())
746 return (endpoint_t
*)fEndpoints
.ItemAt(index
);
751 MidiServerApp::_CountConnections(endpoint_t
* producer
)
753 ASSERT(producer
!= NULL
)
754 ASSERT(!producer
->consumer
)
756 return producer
->connections
.CountItems();
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
);
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");
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",
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
);
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");
828 MidiServerApp
app(status
);
833 return status
== B_OK
? EXIT_SUCCESS
: EXIT_FAILURE
;