* Copyright 2002-2015, Haiku, Inc. All rights reserved.
* Copyright 2002-2004, Matthijs Hollemans
* Copyright 2021, Panagiotis "Ivory" Vasilopoulos <git@n0toose.net>
* Distributed under the terms of the MIT License.
*
* Authors:
* Humdinger
* Matthijs Hollemans
* Oliver Tappe
* Panagiotis "Ivory" Vasilopoulos
* Philippe Houdoin
*/
#include "MidiServerApp.h"
#include <new>
#include <AboutWindow.h>
#include <Catalog.h>
#include <Locale.h>
#include <LocaleRoster.h>
#include "debug.h"
#include "protocol.h"
#include "PortDrivers.h"
#include "ServerDefs.h"
using std::nothrow;
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "midi_server"
MidiServerApp::MidiServerApp(status_t& error)
:
BServer(MIDI_SERVER_SIGNATURE, true, &error)
{
TRACE(("Running Haiku MIDI server"))
fNextID = 1;
fDeviceWatcher = new(std::nothrow) DeviceWatcher();
if (fDeviceWatcher != NULL)
fDeviceWatcher->Run();
}
MidiServerApp::~MidiServerApp()
{
if (fDeviceWatcher && fDeviceWatcher->Lock())
fDeviceWatcher->Quit();
for (int32 t = 0; t < _CountApps(); ++t) {
delete _AppAt(t);
}
for (int32 t = 0; t < _CountEndpoints(); ++t) {
delete _EndpointAt(t);
}
}
void
MidiServerApp::AboutRequested()
{
BAboutWindow* window = new BAboutWindow(B_TRANSLATE_SYSTEM_NAME(
"Haiku MIDI Server"), MIDI_SERVER_SIGNATURE);
window->AddDescription(B_TRANSLATE(
"Notes disguised as bytes\n"
"propagating to endpoints-\n"
"An aural delight."));
const char* extraCopyrights[] = {
"2002-2004 Matthijs Hollemans",
"2021 Panagiotis \"Ivory\" Vasilopoulos",
NULL
};
const char* authors[] = {
"Humdinger",
"Matthijs Hollemans",
"Oliver Tappe",
"Panagiotis \"Ivory\" Vasilopoulos",
"Philippe Houdoin",
NULL
};
window->AddCopyright(2021, "Haiku, Inc.", extraCopyrights);
window->AddAuthors(authors);
window->Show();
}
void
MidiServerApp::MessageReceived(BMessage* msg)
{
#ifdef DEBUG
printf("IN "); msg->PrintToStream();
#endif
switch (msg->what) {
case MSG_REGISTER_APP:
_OnRegisterApp(msg);
break;
case MSG_CREATE_ENDPOINT:
_OnCreateEndpoint(msg);
break;
case MSG_DELETE_ENDPOINT:
_OnDeleteEndpoint(msg);
break;
case MSG_PURGE_ENDPOINT:
_OnPurgeEndpoint(msg);
break;
case MSG_CHANGE_ENDPOINT:
_OnChangeEndpoint(msg);
break;
case MSG_CONNECT_ENDPOINTS:
_OnConnectDisconnect(msg);
break;
case MSG_DISCONNECT_ENDPOINTS:
_OnConnectDisconnect(msg);
break;
default:
super::MessageReceived(msg);
break;
}
}
void
MidiServerApp::_OnRegisterApp(BMessage* msg)
{
TRACE(("MidiServerApp::_OnRegisterApp"))
app_t* app = new app_t;
if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK
&& _SendAllEndpoints(app)
&& _SendAllConnections(app)) {
BMessage reply;
reply.what = MSG_APP_REGISTERED;
if (_SendNotification(app, &reply)) {
fApps.AddItem(app);
#ifdef DEBUG
_DumpApps();
#endif
return;
}
}
delete app;
}
void
MidiServerApp::_OnCreateEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::_OnCreateEndpoint"))
status_t status;
endpoint_t* endpoint = new endpoint_t;
endpoint->app = _WhichApp(msg);
if (endpoint->app == NULL) {
status = B_ERROR;
} else {
status = B_BAD_VALUE;
if (msg->FindBool("midi:consumer", &endpoint->consumer) == B_OK
&& msg->FindBool("midi:registered", &endpoint->registered) == B_OK
&& msg->FindString("midi:name", &endpoint->name) == B_OK
&& msg->FindMessage("midi:properties", &endpoint->properties)
== B_OK) {
if (endpoint->consumer) {
if (msg->FindInt32("midi:port", &endpoint->port) == B_OK
&& msg->FindInt64("midi:latency", &endpoint->latency)
== B_OK)
status = B_OK;
} else
status = B_OK;
}
}
BMessage reply;
if (status == B_OK) {
endpoint->id = fNextID++;
reply.AddInt32("midi:id", endpoint->id);
}
reply.AddInt32("midi:result", status);
if (_SendReply(endpoint->app, msg, &reply) && status == B_OK)
_AddEndpoint(msg, endpoint);
else
delete endpoint;
}
void
MidiServerApp::_OnDeleteEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::_OnDeleteEndpoint"))
app_t* app = _WhichApp(msg);
if (app != NULL) {
endpoint_t* endpoint = _WhichEndpoint(msg, app);
if (endpoint != NULL)
_RemoveEndpoint(app, endpoint);
}
}
void
MidiServerApp::_OnPurgeEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::_OnPurgeEndpoint"))
if (!msg->IsSourceRemote()) {
int32 id;
if (msg->FindInt32("midi:id", &id) == B_OK) {
endpoint_t* endpoint = _FindEndpoint(id);
if (endpoint != NULL)
_RemoveEndpoint(NULL, endpoint);
}
}
}
void
MidiServerApp::_OnChangeEndpoint(BMessage* msg)
{
TRACE(("MidiServerApp::_OnChangeEndpoint"))
endpoint_t* endpoint = NULL;
status_t status;
app_t* app = _WhichApp(msg);
if (app == NULL)
status = B_ERROR;
else {
endpoint = _WhichEndpoint(msg, app);
if (endpoint == NULL)
status = B_BAD_VALUE;
else
status = B_OK;
}
BMessage reply;
reply.AddInt32("midi:result", status);
if (_SendReply(app, msg, &reply) && status == B_OK) {
TRACE(("Endpoint %" B_PRId32 " (%p) changed", endpoint->id, endpoint))
BMessage notify;
notify.what = MSG_ENDPOINT_CHANGED;
notify.AddInt32("midi:id", endpoint->id);
bool registered;
if (msg->FindBool("midi:registered", ®istered) == B_OK) {
notify.AddBool("midi:registered", registered);
endpoint->registered = registered;
}
BString name;
if (msg->FindString("midi:name", &name) == B_OK) {
notify.AddString("midi:name", name);
endpoint->name = name;
}
BMessage properties;
if (msg->FindMessage("midi:properties", &properties) == B_OK) {
notify.AddMessage("midi:properties", &properties);
endpoint->properties = properties;
}
bigtime_t latency;
if (msg->FindInt64("midi:latency", &latency) == B_OK) {
notify.AddInt64("midi:latency", latency);
endpoint->latency = latency;
}
_NotifyAll(¬ify, app);
#ifdef DEBUG
_DumpEndpoints();
#endif
}
}
void
MidiServerApp::_OnConnectDisconnect(BMessage* msg)
{
TRACE(("MidiServerApp::_OnConnectDisconnect"))
bool mustConnect = msg->what == MSG_CONNECT_ENDPOINTS;
status_t status;
endpoint_t* producer = NULL;
endpoint_t* consumer = NULL;
app_t* app = _WhichApp(msg);
if (app == NULL)
status = B_ERROR;
else {
status = B_BAD_VALUE;
int32 producerID;
int32 consumerID;
if (msg->FindInt32("midi:producer", &producerID) == B_OK
&& msg->FindInt32("midi:consumer", &consumerID) == B_OK) {
producer = _FindEndpoint(producerID);
consumer = _FindEndpoint(consumerID);
if (producer != NULL && !producer->consumer) {
if (consumer != NULL && consumer->consumer) {
if (mustConnect == producer->connections.HasItem(consumer))
status = B_ERROR;
else
status = B_OK;
}
}
}
}
BMessage reply;
reply.AddInt32("midi:result", status);
if (_SendReply(app, msg, &reply) && status == B_OK) {
if (mustConnect) {
TRACE(("Connection made: %" B_PRId32 " ---> %" B_PRId32,
producer->id, consumer->id))
producer->connections.AddItem(consumer);
} else {
TRACE(("Connection broken: %" B_PRId32 " -X-> %" B_PRId32,
producer->id, consumer->id))
producer->connections.RemoveItem(consumer);
}
BMessage notify;
_MakeConnectedNotification(¬ify, producer, consumer, mustConnect);
_NotifyAll(¬ify, app);
#ifdef DEBUG
_DumpEndpoints();
#endif
}
}
all current endpoints. Used when the app registers.
*/
bool
MidiServerApp::_SendAllEndpoints(app_t* app)
{
ASSERT(app != NULL)
BMessage notify;
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* endpoint = _EndpointAt(t);
_MakeCreatedNotification(¬ify, endpoint);
if (!_SendNotification(app, ¬ify))
return false;
}
return true;
}
all current connections. Used when the app registers.
*/
bool
MidiServerApp::_SendAllConnections(app_t* app)
{
ASSERT(app != NULL)
BMessage notify;
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* producer = _EndpointAt(t);
if (!producer->consumer) {
for (int32 k = 0; k < _CountConnections(producer); ++k) {
endpoint_t* consumer = _ConnectionAt(producer, k);
_MakeConnectedNotification(¬ify, producer, consumer, true);
if (!_SendNotification(app, ¬ify))
return false;
}
}
}
return true;
}
all other applications about this event.
*/
void
MidiServerApp::_AddEndpoint(BMessage* msg, endpoint_t* endpoint)
{
ASSERT(msg != NULL)
ASSERT(endpoint != NULL)
ASSERT(!fEndpoints.HasItem(endpoint))
TRACE(("Endpoint %" B_PRId32 " (%p) added", endpoint->id, endpoint))
fEndpoints.AddItem(endpoint);
BMessage notify;
_MakeCreatedNotification(¬ify, endpoint);
_NotifyAll(¬ify, endpoint->app);
#ifdef DEBUG
_DumpEndpoints();
#endif
}
other apps about this event. "app" is the application
that the endpoint belongs to; if it is NULL, the app
no longer exists and we're purging the endpoint.
*/
void
MidiServerApp::_RemoveEndpoint(app_t* app, endpoint_t* endpoint)
{
ASSERT(endpoint != NULL)
ASSERT(fEndpoints.HasItem(endpoint))
TRACE(("Endpoint %" B_PRId32 " (%p) removed", endpoint->id, endpoint))
fEndpoints.RemoveItem(endpoint);
if (endpoint->consumer)
_DisconnectDeadConsumer(endpoint);
BMessage notify;
notify.what = MSG_ENDPOINT_DELETED;
notify.AddInt32("midi:id", endpoint->id);
_NotifyAll(¬ify, app);
delete endpoint;
#ifdef DEBUG
_DumpEndpoints();
#endif
}
all the producers it is connected to, just before
we remove it from the roster.
*/
void
MidiServerApp::_DisconnectDeadConsumer(endpoint_t* consumer)
{
ASSERT(consumer != NULL)
ASSERT(consumer->consumer)
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* producer = _EndpointAt(t);
if (!producer->consumer)
producer->connections.RemoveItem(consumer);
}
}
void
MidiServerApp::_MakeCreatedNotification(BMessage* msg, endpoint_t* endpoint)
{
ASSERT(msg != NULL)
ASSERT(endpoint != NULL)
msg->MakeEmpty();
msg->what = MSG_ENDPOINT_CREATED;
msg->AddInt32("midi:id", endpoint->id);
msg->AddBool("midi:consumer", endpoint->consumer);
msg->AddBool("midi:registered", endpoint->registered);
msg->AddString("midi:name", endpoint->name);
msg->AddMessage("midi:properties", &endpoint->properties);
if (endpoint->consumer) {
msg->AddInt32("midi:port", endpoint->port);
msg->AddInt64("midi:latency", endpoint->latency);
}
}
void
MidiServerApp::_MakeConnectedNotification(BMessage* msg, endpoint_t* producer,
endpoint_t* consumer, bool mustConnect)
{
ASSERT(msg != NULL)
ASSERT(producer != NULL)
ASSERT(consumer != NULL)
ASSERT(!producer->consumer)
ASSERT(consumer->consumer)
msg->MakeEmpty();
if (mustConnect)
msg->what = MSG_ENDPOINTS_CONNECTED;
else
msg->what = MSG_ENDPOINTS_DISCONNECTED;
msg->AddInt32("midi:producer", producer->id);
msg->AddInt32("midi:consumer", consumer->id);
}
Returns NULL if the application is not registered.
*/
app_t*
MidiServerApp::_WhichApp(BMessage* msg)
{
ASSERT(msg != NULL)
BMessenger retadr = msg->ReturnAddress();
for (int32 t = 0; t < _CountApps(); ++t) {
app_t* app = _AppAt(t);
if (app->messenger.Team() == retadr.Team())
return app;
}
TRACE(("Application %" B_PRId32 " is not registered", retadr.Team()))
return NULL;
}
the endpoint object that corresponds to that ID. It also
checks whether the application specified by "app" really
owns the endpoint. Returns NULL on error.
*/
endpoint_t*
MidiServerApp::_WhichEndpoint(BMessage* msg, app_t* app)
{
ASSERT(msg != NULL)
ASSERT(app != NULL)
int32 id;
if (msg->FindInt32("midi:id", &id) == B_OK) {
endpoint_t* endpoint = _FindEndpoint(id);
if (endpoint != NULL && endpoint->app == app)
return endpoint;
}
TRACE(("Endpoint not found or wrong app"))
return NULL;
}
\c NULL if no such endpoint exists on the roster.
*/
endpoint_t*
MidiServerApp::_FindEndpoint(int32 id)
{
if (id > 0) {
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* endpoint = _EndpointAt(t);
if (endpoint->id == id)
return endpoint;
}
}
TRACE(("Endpoint %" B_PRId32 " not found", id))
return NULL;
}
except to the application that triggered the event.
The "except" app is allowed to be NULL.
*/
void
MidiServerApp::_NotifyAll(BMessage* msg, app_t* except)
{
ASSERT(msg != NULL)
for (int32 t = _CountApps() - 1; t >= 0; --t) {
app_t* app = _AppAt(t);
if (app != except && !_SendNotification(app, msg)) {
delete (app_t*)fApps.RemoveItem(t);
#ifdef DEBUG
_DumpApps();
#endif
}
}
}
not necessarily registered yet. Applications never reply
to such notification messages.
*/
bool
MidiServerApp::_SendNotification(app_t* app, BMessage* msg)
{
ASSERT(app != NULL)
ASSERT(msg != NULL)
status_t status = app->messenger.SendMessage(msg, (BHandler*) NULL,
TIMEOUT);
if (status != B_OK)
_DeliveryError(app);
return status == B_OK;
}
If "app" is NULL, the application is not registered
(and the reply should contain an error code).
*/
bool
MidiServerApp::_SendReply(app_t* app, BMessage* msg, BMessage* reply)
{
ASSERT(msg != NULL)
ASSERT(reply != NULL)
status_t status = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
if (status != B_OK && app != NULL) {
_DeliveryError(app);
fApps.RemoveItem(app);
delete app;
#ifdef DEBUG
_DumpApps();
#endif
}
return status == B_OK;
}
if a reply or notification message cannot be delivered.
(Waiting for communications to fail is actually our only
way to get rid of stale endpoints.)
*/
void
MidiServerApp::_DeliveryError(app_t* app)
{
ASSERT(app != NULL)
TRACE(("Delivery error; unregistering app (%p)", app))
BMessage msg;
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* endpoint = _EndpointAt(t);
if (endpoint->app == app) {
msg.MakeEmpty();
msg.what = MSG_PURGE_ENDPOINT;
msg.AddInt32("midi:id", endpoint->id);
if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
TIMEOUT) != B_OK) {
WARN("Could not deliver purge message")
}
}
}
}
int32
MidiServerApp::_CountApps()
{
return fApps.CountItems();
}
app_t*
MidiServerApp::_AppAt(int32 index)
{
ASSERT(index >= 0 && index < _CountApps())
return (app_t*)fApps.ItemAt(index);
}
int32
MidiServerApp::_CountEndpoints()
{
return fEndpoints.CountItems();
}
endpoint_t*
MidiServerApp::_EndpointAt(int32 index)
{
ASSERT(index >= 0 && index < _CountEndpoints())
return (endpoint_t*)fEndpoints.ItemAt(index);
}
int32
MidiServerApp::_CountConnections(endpoint_t* producer)
{
ASSERT(producer != NULL)
ASSERT(!producer->consumer)
return producer->connections.CountItems();
}
endpoint_t*
MidiServerApp::_ConnectionAt(endpoint_t* producer, int32 index)
{
ASSERT(producer != NULL)
ASSERT(!producer->consumer)
ASSERT(index >= 0 && index < _CountConnections(producer))
return (endpoint_t*)producer->connections.ItemAt(index);
}
#ifdef DEBUG
void
MidiServerApp::_DumpApps()
{
printf("*** START DumpApps\n");
for (int32 t = 0; t < _CountApps(); ++t) {
app_t* app = _AppAt(t);
printf("\tapp %" B_PRId32 " (%p): team %" B_PRId32 "\n", t, app,
app->messenger.Team());
}
printf("*** END DumpApps\n");
}
void
MidiServerApp::_DumpEndpoints()
{
printf("*** START DumpEndpoints\n");
for (int32 t = 0; t < _CountEndpoints(); ++t) {
endpoint_t* endpoint = _EndpointAt(t);
printf("\tendpoint %" B_PRId32 " (%p):\n", t, endpoint);
printf("\t\tid %" B_PRId32 ", name '%s', %s, %s, app %p\n",
endpoint->id, endpoint->name.String(),
endpoint->consumer ? "consumer" : "producer",
endpoint->registered ? "registered" : "unregistered",
endpoint->app);
printf("\t\tproperties: "); endpoint->properties.PrintToStream();
if (endpoint->consumer)
printf("\t\tport %" B_PRId32 ", latency %" B_PRIdBIGTIME "\n",
endpoint->port, endpoint->latency);
else {
printf("\t\tconnections:\n");
for (int32 k = 0; k < _CountConnections(endpoint); ++k) {
endpoint_t* consumer = _ConnectionAt(endpoint, k);
printf("\t\t\tid %" B_PRId32 " (%p)\n", consumer->id, consumer);
}
}
}
printf("*** END DumpEndpoints\n");
}
#endif
int
main()
{
status_t status;
MidiServerApp app(status);
if (status == B_OK)
app.Run();
return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}