From 53284a9fdba431c73476c6354a9cc999f7a12e9f Mon Sep 17 00:00:00 2001 From: mahlzeit Date: Tue, 31 Dec 2002 14:34:17 +0000 Subject: [PATCH] Checkin of the source code for midi2 kit milestone 1. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@2337 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/kits/midi2/Jamfile | 10 +- src/kits/midi2/MidiConsumer.cpp | 23 +- src/kits/midi2/MidiEndpoint.cpp | 298 ++++++++-- src/kits/midi2/MidiLocalConsumer.cpp | 49 +- src/kits/midi2/MidiLocalProducer.cpp | 40 +- src/kits/midi2/MidiProducer.cpp | 219 ++++++-- src/kits/midi2/MidiRoster.cpp | 497 ++++++++++------- src/kits/midi2/MidiRosterLooper.cpp | 719 ++++++++++++++++++++++++ src/kits/midi2/MidiRosterLooper.h | 145 +++++ src/servers/midi/Jamfile | 2 - src/servers/midi/MidiServerApp.cpp | 788 ++++++++++++++++++++++++++- src/servers/midi/MidiServerApp.h | 138 ++++- src/servers/midi/ServerDefs.h | 74 +++ 13 files changed, 2664 insertions(+), 338 deletions(-) create mode 100644 src/kits/midi2/MidiRosterLooper.cpp create mode 100644 src/kits/midi2/MidiRosterLooper.h create mode 100644 src/servers/midi/ServerDefs.h diff --git a/src/kits/midi2/Jamfile b/src/kits/midi2/Jamfile index 30112c4a14..8b96199356 100644 --- a/src/kits/midi2/Jamfile +++ b/src/kits/midi2/Jamfile @@ -5,15 +5,15 @@ UsePrivateHeaders midi ; UsePublicHeaders midi2 ; SharedLibrary midi2 : - MidiRoster.cpp - MidiEndpoint.cpp MidiConsumer.cpp + MidiEndpoint.cpp MidiLocalConsumer.cpp - MidiProducer.cpp MidiLocalProducer.cpp + MidiProducer.cpp + MidiRoster.cpp + MidiRosterLooper.cpp ; LinkSharedOSLibs libmidi2.so : - be root + be ; - diff --git a/src/kits/midi2/MidiConsumer.cpp b/src/kits/midi2/MidiConsumer.cpp index fd84797d56..463500e9c5 100644 --- a/src/kits/midi2/MidiConsumer.cpp +++ b/src/kits/midi2/MidiConsumer.cpp @@ -1,19 +1,28 @@ /** * @file MidiConsumer.cpp * + * Implementation of the BMidiConsumer class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include "debug.h" #include "MidiConsumer.h" -#include "MidiEndpoint.h" +#include "protocol.h" //------------------------------------------------------------------------------ bigtime_t BMidiConsumer::Latency() const { - return fLatency; + bigtime_t res = 0LL; + + if (LockLooper()) + { + res = latency; + UnlockLooper(); + } + + return res; } //------------------------------------------------------------------------------ @@ -21,15 +30,16 @@ bigtime_t BMidiConsumer::Latency() const BMidiConsumer::BMidiConsumer(const char* name) : BMidiEndpoint(name) { - fLatency = 0; + isConsumer = true; + latency = 0LL; + port = 0; } //------------------------------------------------------------------------------ BMidiConsumer::~BMidiConsumer() { - if (fEventPort != B_NO_MORE_PORTS) - delete_port(fEventPort); + // Do nothing. } //------------------------------------------------------------------------------ @@ -44,4 +54,3 @@ void BMidiConsumer::_Reserved7() { } void BMidiConsumer::_Reserved8() { } //------------------------------------------------------------------------------ - diff --git a/src/kits/midi2/MidiEndpoint.cpp b/src/kits/midi2/MidiEndpoint.cpp index 5665225b42..d78051e798 100644 --- a/src/kits/midi2/MidiEndpoint.cpp +++ b/src/kits/midi2/MidiEndpoint.cpp @@ -1,73 +1,110 @@ /** * @file MidiEndpoint.cpp * + * Implementation of the BMidiEndpoint class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ -#include - #include "debug.h" #include "MidiEndpoint.h" +#include "MidiRoster.h" +#include "MidiRosterLooper.h" +#include "protocol.h" //------------------------------------------------------------------------------ const char* BMidiEndpoint::Name() const { - return fName.String(); + const char* str = NULL; + + // It seems reasonable to assume that the pointer + // returned by BString::String() can change when the + // string is modified, e.g. to allocate more space. + // That's why we need to lock here too. + + if (LockLooper()) + { + str = name.String(); + UnlockLooper(); + } + + return str; } //------------------------------------------------------------------------------ -void BMidiEndpoint::SetName(const char* name) +void BMidiEndpoint::SetName(const char* name_) { - BMidiRoster *roster = BMidiRoster::MidiRoster(); - roster->Rename(this, name); - fName.SetTo(name); + if (name_ == NULL) + { + WARN("SetName() does not accept a NULL name"); + return; + } + else if (IsRemote()) + { + WARN("SetName() is not allowed on remote endpoints"); + return; + } + else if (!IsValid()) + { + return; + } + else if (name != name_) + { + BMessage msg; + msg.AddString("midi:name", name_); + + if (SendChangeRequest(&msg) == B_OK) + { + if (LockLooper()) + { + name.SetTo(name_); + UnlockLooper(); + } + } + } } //------------------------------------------------------------------------------ int32 BMidiEndpoint::ID() const { - return fID; + return id; } //------------------------------------------------------------------------------ bool BMidiEndpoint::IsProducer() const { -return (fFlags && 0x01) == 0x01; + return !isConsumer; } //------------------------------------------------------------------------------ bool BMidiEndpoint::IsConsumer() const { -return (fFlags && 0x01) == 0x01; + return isConsumer; } //------------------------------------------------------------------------------ bool BMidiEndpoint::IsRemote() const { - UNIMPLEMENTED - return false; + return !isLocal; } //------------------------------------------------------------------------------ bool BMidiEndpoint::IsLocal() const { - UNIMPLEMENTED - return false; + return isLocal; } //------------------------------------------------------------------------------ bool BMidiEndpoint::IsPersistent() const { - UNIMPLEMENTED return false; } @@ -75,82 +112,194 @@ bool BMidiEndpoint::IsPersistent() const bool BMidiEndpoint::IsValid() const { - UNIMPLEMENTED - return false; + if (IsLocal()) + { + return (ID() > 0); + } + else // remote endpoint + { + return IsRegistered(); + } } //------------------------------------------------------------------------------ status_t BMidiEndpoint::Release() { - if (1 == atomic_add(&fRefCount, -1)) + int32 old = atomic_add(&refCount, -1); + + TRACE(("BMidiEndpoint::Release refCount is now %ld", old - 1)) + + if (old == 1) { - Unregister(); - delete this; - return B_OK; + // If the reference count of a local endpoint drops to zero, + // we must delete it. The destructor of BMidiLocalXXX calls + // BMidiRoster::DeleteLocal(), which does all the hard work. + // If we are a proxy for a remote endpoint, we must only be + // deleted if that remote endpoint no longer exists. + + if (IsLocal() || !isAlive) + { + delete this; + } } - else + else if (old <= 0) { - return B_ERROR; + debugger("too many calls to Release()"); } + + return B_OK; } //------------------------------------------------------------------------------ status_t BMidiEndpoint::Acquire() { - atomic_add(&fRefCount, 1); + int32 old = atomic_add(&refCount, 1); + + TRACE(("BMidiEndpoint::Acquire refCount is now %ld", old + 1)) + return B_OK; } //------------------------------------------------------------------------------ -status_t BMidiEndpoint::SetProperties(const BMessage* props) +status_t BMidiEndpoint::SetProperties(const BMessage* properties_) { - UNIMPLEMENTED - return B_ERROR; + if (properties_ == NULL) + { + WARN("SetProperties() does not accept a NULL message") + return B_BAD_VALUE; + } + else if (IsRemote()) + { + WARN("SetProperties() is not allowed on remote endpoints"); + return B_ERROR; + } + else if (!IsValid()) + { + return B_ERROR; + } + else + { + BMessage msg; + msg.AddMessage("midi:properties", properties_); + + status_t err = SendChangeRequest(&msg); + if (err == B_OK) + { + if (LockLooper()) + { + *properties = *properties_; + UnlockLooper(); + } + } + + return err; + } } //------------------------------------------------------------------------------ -status_t BMidiEndpoint::GetProperties(BMessage* props) const +status_t BMidiEndpoint::GetProperties(BMessage* properties_) const { - UNIMPLEMENTED - return B_ERROR; + if (properties_ == NULL) + { + WARN("GetProperties() does not accept NULL properties") + return B_BAD_VALUE; + } + + if (LockLooper()) + { + *properties_ = *properties; + UnlockLooper(); + } + + return B_OK; } //------------------------------------------------------------------------------ status_t BMidiEndpoint::Register(void) { - UNIMPLEMENTED - return B_ERROR; + if (IsRemote()) + { + WARN("You cannot Register() remote endpoints"); + return B_ERROR; + } + else if (IsRegistered()) + { + WARN("This endpoint is already registered"); + return B_OK; + } + else if (!IsValid()) + { + return B_ERROR; + } + else + { + return SendRegisterRequest(true); + } } //------------------------------------------------------------------------------ status_t BMidiEndpoint::Unregister(void) { - UNIMPLEMENTED - return B_ERROR; + if (IsRemote()) + { + WARN("You cannot Unregister() remote endpoints"); + return B_ERROR; + } + else if (!IsRegistered()) + { + WARN("This endpoint is already unregistered"); + return B_OK; + } + else if (!IsValid()) + { + return B_ERROR; + } + else + { + return SendRegisterRequest(false); + } } //------------------------------------------------------------------------------ -BMidiEndpoint::BMidiEndpoint(const char* name) +BMidiEndpoint::BMidiEndpoint(const char* name_) { - fName = BString(name); - fStatus = B_OK; - fFlags = 0; - fRefCount = 0; -// fID = MidiRosterApp::GetNextFreeID(); + TRACE(("BMidiEndpoint::BMidiEndpoint")) + + if (name_ != NULL) + { + name.SetTo(name_); + } + + id = 0; + refCount = 0; + isLocal = false; + isRegistered = false; + isAlive = true; + + properties = new BMessage; } //------------------------------------------------------------------------------ BMidiEndpoint::~BMidiEndpoint() { - UNIMPLEMENTED + TRACE(("BMidiEndpoint::~BMidiEndpoint (%p)", this)) + + if (refCount > 0) + { + debugger( + "you should use Release() to dispose of endpoints; " + "do not \"delete\" them or allocate them on the stack!"); + } + + delete properties; } //------------------------------------------------------------------------------ @@ -166,3 +315,68 @@ void BMidiEndpoint::_Reserved8() { } //------------------------------------------------------------------------------ +status_t BMidiEndpoint::SendRegisterRequest(bool registered) +{ + BMessage msg; + msg.AddBool("midi:registered", registered); + + status_t err = SendChangeRequest(&msg); + if (err == B_OK) + { + if (LockLooper()) + { + isRegistered = registered; + UnlockLooper(); + } + } + + return err; +} + +//------------------------------------------------------------------------------ + +status_t BMidiEndpoint::SendChangeRequest(BMessage* msg) +{ + ASSERT(msg != NULL) + + msg->what = MSG_CHANGE_ENDPOINT; + msg->AddInt32("midi:id", ID()); + + BMessage reply; + status_t err = BMidiRoster::MidiRoster()->SendRequest(msg, &reply); + if (err != B_OK) { return err; } + + status_t res; + if (reply.FindInt32("midi:result", &res) == B_OK) + { + return res; + } + + return B_ERROR; +} + +//------------------------------------------------------------------------------ + +bool BMidiEndpoint::IsRegistered() const +{ + // No need to protect this with a lock, because reading + // and writing a bool is always an atomic operation. + + return isRegistered; +} + +//------------------------------------------------------------------------------ + +bool BMidiEndpoint::LockLooper() const +{ + return BMidiRoster::MidiRoster()->looper->Lock(); +} + +//------------------------------------------------------------------------------ + +void BMidiEndpoint::UnlockLooper() const +{ + BMidiRoster::MidiRoster()->looper->Unlock(); +} + +//------------------------------------------------------------------------------ diff --git a/src/kits/midi2/MidiLocalConsumer.cpp b/src/kits/midi2/MidiLocalConsumer.cpp index dca14b9221..a913e2d2fe 100644 --- a/src/kits/midi2/MidiLocalConsumer.cpp +++ b/src/kits/midi2/MidiLocalConsumer.cpp @@ -1,48 +1,80 @@ /** * @file MidiLocalConsumer.cpp * + * Implementation of the BMidiLocalConsumer class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include "debug.h" #include "MidiConsumer.h" +#include "MidiRoster.h" +#include "protocol.h" //------------------------------------------------------------------------------ BMidiLocalConsumer::BMidiLocalConsumer(const char* name) : BMidiConsumer(name) { - fFlags |= 0x10; + TRACE(("BMidiLocalConsumer::BMidiLocalConsumer")) + + isLocal = true; + refCount = 1; + + BMidiRoster::MidiRoster()->CreateLocal(this); } //------------------------------------------------------------------------------ BMidiLocalConsumer::~BMidiLocalConsumer() { - UNIMPLEMENTED + TRACE(("BMidiLocalConsumer::~BMidiLocalConsumer")) + + BMidiRoster::MidiRoster()->DeleteLocal(this); } //------------------------------------------------------------------------------ -void BMidiLocalConsumer::SetLatency(bigtime_t latency) +void BMidiLocalConsumer::SetLatency(bigtime_t latency_) { - fLatency = latency; + if (latency_ < 0) + { + WARN("SetLatency() does not accept negative values"); + return; + } + else if (!IsValid()) + { + return; + } + else if (latency != latency_) + { + BMessage msg; + msg.AddInt64("midi:latency", latency_); + + if (SendChangeRequest(&msg) == B_OK) + { + if (LockLooper()) + { + latency = latency_; + UnlockLooper(); + } + } + } } //------------------------------------------------------------------------------ int32 BMidiLocalConsumer::GetProducerID(void) { -return fCurrentProducer; + UNIMPLEMENTED + return 0; } //------------------------------------------------------------------------------ void BMidiLocalConsumer::SetTimeout(bigtime_t when, void* data) { - fTimeout = when; - fTimeoutData = data; + UNIMPLEMENTED } //------------------------------------------------------------------------------ @@ -165,4 +197,3 @@ void BMidiLocalConsumer::_Reserved7() { } void BMidiLocalConsumer::_Reserved8() { } //------------------------------------------------------------------------------ - diff --git a/src/kits/midi2/MidiLocalProducer.cpp b/src/kits/midi2/MidiLocalProducer.cpp index 1f03cc868d..ebd7ac2c51 100644 --- a/src/kits/midi2/MidiLocalProducer.cpp +++ b/src/kits/midi2/MidiLocalProducer.cpp @@ -1,40 +1,57 @@ /** * @file MidiLocalProducer.cpp * + * Implementation of the BMidiLocalProducer class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include "debug.h" +#include "MidiConsumer.h" #include "MidiProducer.h" +#include "MidiRoster.h" +#include "protocol.h" //------------------------------------------------------------------------------ BMidiLocalProducer::BMidiLocalProducer(const char* name) : BMidiProducer(name) { - fFlags |= 0x10; + TRACE(("BMidiLocalProducer::BMidiLocalProducer")) + + isLocal = true; + refCount = 1; + + BMidiRoster::MidiRoster()->CreateLocal(this); } //------------------------------------------------------------------------------ BMidiLocalProducer::~BMidiLocalProducer() { - UNIMPLEMENTED + TRACE(("BMidiLocalProducer::~BMidiLocalProducer")) + + BMidiRoster::MidiRoster()->DeleteLocal(this); } //------------------------------------------------------------------------------ -void BMidiLocalProducer::Connected(BMidiConsumer* dest) +void BMidiLocalProducer::Connected(BMidiConsumer* cons) { - UNIMPLEMENTED + ASSERT(cons != NULL) + TRACE(("Connected() %ld to %ld", ID(), cons->ID())) + + // Do nothing. } //------------------------------------------------------------------------------ -void BMidiLocalProducer::Disconnected(BMidiConsumer* dest) +void BMidiLocalProducer::Disconnected(BMidiConsumer* cons) { - UNIMPLEMENTED + ASSERT(cons != NULL) + TRACE(("Disconnected() %ld from %ld", ID(), cons->ID())) + + // Do nothing. } //------------------------------------------------------------------------------ @@ -136,14 +153,6 @@ void BMidiLocalProducer::SprayTempoChange( //------------------------------------------------------------------------------ -void BMidiLocalProducer::SprayEvent( - BMidiEvent* event, size_t length) const -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - void BMidiLocalProducer::_Reserved1() { } void BMidiLocalProducer::_Reserved2() { } void BMidiLocalProducer::_Reserved3() { } @@ -154,4 +163,3 @@ void BMidiLocalProducer::_Reserved7() { } void BMidiLocalProducer::_Reserved8() { } //------------------------------------------------------------------------------ - diff --git a/src/kits/midi2/MidiProducer.cpp b/src/kits/midi2/MidiProducer.cpp index f6bc015f46..e55478fd49 100644 --- a/src/kits/midi2/MidiProducer.cpp +++ b/src/kits/midi2/MidiProducer.cpp @@ -1,64 +1,93 @@ /** * @file MidiProducer.cpp * + * Implementation of the BMidiProducer class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include "debug.h" +#include "MidiConsumer.h" #include "MidiProducer.h" -#include "MidiEndpoint.h" +#include "MidiRoster.h" +#include "MidiRosterLooper.h" +#include "protocol.h" //------------------------------------------------------------------------------ -status_t BMidiProducer::Connect(BMidiConsumer* toObject) +status_t BMidiProducer::Connect(BMidiConsumer* cons) { -/* - if (toObject != NULL) + if (cons == NULL) { - if (fConnections->Add(toObject) == true) - { - fConnectionCount++; - } + WARN("Connect() does not accept a NULL consumer") + return B_BAD_VALUE; + } + else if (!IsValid() || !cons->IsValid()) + { + return B_ERROR; + } + else + { + return SendConnectRequest(cons, true); } - BMidiRoster *roster = BMidiRoster::MidiRoster(); - return roster->Connect(this, toObject); -*/ - return B_ERROR; } //------------------------------------------------------------------------------ -status_t BMidiProducer::Disconnect(BMidiConsumer* toObject) +status_t BMidiProducer::Disconnect(BMidiConsumer* cons) { -/* - if (toObject != NULL) + if (cons == NULL) { - if (fConnections->Remove(toObject) == true) - { - fConnectionCount--; - BMidiRoster *roster = BMidiRoster::MidiRoster(); - return roster->Disconnect(this, toObject); - } + WARN("Disconnect() does not accept a NULL consumer") + return B_BAD_VALUE; + } + else if (!IsValid() || !cons->IsValid()) + { + return B_ERROR; + } + else + { + return SendConnectRequest(cons, false); } -*/ - return B_ERROR; } //------------------------------------------------------------------------------ -bool BMidiProducer::IsConnected(BMidiConsumer* toObject) const +bool BMidiProducer::IsConnected(BMidiConsumer* cons) const { -// return fConnections->IsIn(toObject); - return false; + bool isConnected = false; + + if (cons != NULL) + { + if (LockProducer()) + { + isConnected = connections->HasItem(cons); + UnlockProducer(); + } + } + + return isConnected; } //------------------------------------------------------------------------------ BList* BMidiProducer::Connections() const { -// return fConnections; - return NULL; + BList* list = new BList(); + + if (LockProducer()) + { + for (int32 t = 0; t < CountConsumers(); ++t) + { + BMidiConsumer* cons = ConsumerAt(t); + cons->Acquire(); + list->AddItem(cons); + } + + UnlockProducer(); + } + + return list; } //------------------------------------------------------------------------------ @@ -66,18 +95,17 @@ BList* BMidiProducer::Connections() const BMidiProducer::BMidiProducer(const char* name) : BMidiEndpoint(name) { -/* - fConnections = new BMidiList(); - fConnectionCount = 1; - fLock = BLocker("BMidiProducer Lock"); -*/ + isConsumer = false; + connections = new BList; + locker = new BLocker(); } //------------------------------------------------------------------------------ BMidiProducer::~BMidiProducer() { - UNIMPLEMENTED + delete connections; + delete locker; } //------------------------------------------------------------------------------ @@ -93,3 +121,124 @@ void BMidiProducer::_Reserved8() { } //------------------------------------------------------------------------------ +status_t BMidiProducer::SendConnectRequest( + BMidiConsumer* cons, bool mustConnect) +{ + ASSERT(cons != NULL) + + BMessage msg, reply; + + if (mustConnect) + { + msg.what = MSG_CONNECT_ENDPOINTS; + } + else + { + msg.what = MSG_DISCONNECT_ENDPOINTS; + } + + msg.AddInt32("midi:producer", ID()); + msg.AddInt32("midi:consumer", cons->ID()); + + status_t err = BMidiRoster::MidiRoster()->SendRequest(&msg, &reply); + if (err != B_OK) { return err; } + + status_t res; + if (reply.FindInt32("midi:result", &res) == B_OK) + { + if (res == B_OK) + { + if (mustConnect) + { + ConnectionMade(cons); + } + else + { + ConnectionBroken(cons); + } + + #ifdef DEBUG + BMidiRoster::MidiRoster()->looper->DumpEndpoints(); + #endif + } + + return res; + } + + return B_ERROR; +} + +//------------------------------------------------------------------------------ + +void BMidiProducer::ConnectionMade(BMidiConsumer* cons) +{ + ASSERT(cons != NULL) + + if (LockProducer()) + { + ASSERT(!connections->HasItem(cons)) + + connections->AddItem(cons); + UnlockProducer(); + } + + if (IsLocal()) + { + ((BMidiLocalProducer*) this)->Connected(cons); + } +} + +//------------------------------------------------------------------------------ + +bool BMidiProducer::ConnectionBroken(BMidiConsumer* cons) +{ + ASSERT(cons != NULL) + + bool wasConnected = false; + + if (LockProducer()) + { + wasConnected = connections->RemoveItem(cons); + UnlockProducer(); + } + + if (wasConnected && IsLocal()) + { + ((BMidiLocalProducer*) this)->Disconnected(cons); + } + + return wasConnected; +} + +//------------------------------------------------------------------------------ + +int32 BMidiProducer::CountConsumers() const +{ + return connections->CountItems(); +} + +//------------------------------------------------------------------------------ + +BMidiConsumer* BMidiProducer::ConsumerAt(int32 index) const +{ + ASSERT(connections != NULL) + ASSERT(index >= 0 && index < CountConsumers()) + + return (BMidiConsumer*) connections->ItemAt(index); +} + +//------------------------------------------------------------------------------ + +bool BMidiProducer::LockProducer() const +{ + return locker->Lock(); +} + +//------------------------------------------------------------------------------ + +void BMidiProducer::UnlockProducer() const +{ + locker->Unlock(); +} + +//------------------------------------------------------------------------------ diff --git a/src/kits/midi2/MidiRoster.cpp b/src/kits/midi2/MidiRoster.cpp index 45e065caf7..d010f52277 100644 --- a/src/kits/midi2/MidiRoster.cpp +++ b/src/kits/midi2/MidiRoster.cpp @@ -1,26 +1,90 @@ /** * @file MidiRoster.cpp * + * Implementation of the BMidiRoster class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include "debug.h" +#include "MidiConsumer.h" #include "MidiRoster.h" +#include "MidiRosterLooper.h" +#include "protocol.h" + +// The midi_debug_level and midi_dispatcher_priority symbols +// were exported by Be's libmidi2, and even though they do not +// appear in the headers, some apps may still be using them. +// For backwards compatibility's sake, we export those symbols +// as well, even though we do not use them for anything. + +/** Not used. For backwards compatibility only. */ +int32 midi_debug_level = 0; + +/** Not used. For backwards compatibility only. */ +int32 midi_dispatcher_priority = B_REAL_TIME_PRIORITY; + +//------------------------------------------------------------------------------ + +/** + * The one and only BMidiRoster instance, which is created + * the first time the client app calls MidiRoster(). It is + * destroyed by the BMidiRosterKiller when the app quits. + */ +static BMidiRoster* roster = NULL; + +/** Destroys the BMidiRoster instance when the app quits. */ +static class BMidiRosterKiller +{ +public: + ~BMidiRosterKiller() + { + delete roster; + } +} +killer; //------------------------------------------------------------------------------ BMidiEndpoint* BMidiRoster::NextEndpoint(int32* id) { - UNIMPLEMENTED - return NULL; -} + BMidiEndpoint* endp = NULL; + + if (id != NULL) + { + BMidiRosterLooper* looper = MidiRoster()->looper; + if (looper->Lock()) + { + endp = looper->NextEndpoint(id); + if (endp != NULL) + { + endp->Acquire(); + } + looper->Unlock(); + } + } + + return endp; +} //------------------------------------------------------------------------------ BMidiProducer* BMidiRoster::NextProducer(int32* id) { - UNIMPLEMENTED + BMidiEndpoint* endp; + + while ((endp = NextEndpoint(id)) != NULL) + { + if (endp->IsProducer()) + { + return (BMidiProducer*) endp; + } + else + { + endp->Release(); + } + } + return NULL; } @@ -28,84 +92,200 @@ BMidiProducer* BMidiRoster::NextProducer(int32* id) BMidiConsumer* BMidiRoster::NextConsumer(int32* id) { - UNIMPLEMENTED + BMidiEndpoint* endp; + + while ((endp = NextEndpoint(id)) != NULL) + { + if (endp->IsConsumer()) + { + return (BMidiConsumer*) endp; + } + else + { + endp->Release(); + } + } + return NULL; } //------------------------------------------------------------------------------ -BMidiEndpoint* BMidiRoster::FindEndpoint(int32 id, bool local_only) +BMidiEndpoint* BMidiRoster::FindEndpoint(int32 id, bool localOnly) { - UNIMPLEMENTED - return NULL; + BMidiEndpoint* endp = NULL; + + BMidiRosterLooper* looper = MidiRoster()->looper; + if (looper->Lock()) + { + endp = looper->FindEndpoint(id); + + if ((endp != NULL) && endp->IsRemote()) + { + if (localOnly || !endp->IsRegistered()) + { + endp = NULL; + } + } + + if (endp != NULL) + { + endp->Acquire(); + } + + looper->Unlock(); + } + + return endp; +} + +//------------------------------------------------------------------------------ + +BMidiProducer* BMidiRoster::FindProducer(int32 id, bool localOnly) +{ + BMidiEndpoint* endp = FindEndpoint(id, localOnly); + + if ((endp != NULL) && !endp->IsProducer()) + { + endp->Release(); + endp = NULL; + } + + return (BMidiProducer*) endp; } //------------------------------------------------------------------------------ -BMidiProducer* BMidiRoster::FindProducer(int32 id, bool local_only) +BMidiConsumer* BMidiRoster::FindConsumer(int32 id, bool localOnly) { - UNIMPLEMENTED - return NULL; -} + BMidiEndpoint* endp = FindEndpoint(id, localOnly); -//------------------------------------------------------------------------------ + if ((endp != NULL) && !endp->IsConsumer()) + { + endp->Release(); + endp = NULL; + } -BMidiConsumer* BMidiRoster::FindConsumer(int32 id, bool local_only) -{ - UNIMPLEMENTED - return NULL; + return (BMidiConsumer*) endp; } //------------------------------------------------------------------------------ void BMidiRoster::StartWatching(const BMessenger* msngr) { - UNIMPLEMENTED + if (msngr == NULL) + { + WARN("StartWatching does not accept a NULL messenger") + } + else + { + BMidiRosterLooper* looper = MidiRoster()->looper; + if (looper->Lock()) + { + looper->StartWatching(msngr); + looper->Unlock(); + } + } } //------------------------------------------------------------------------------ void BMidiRoster::StopWatching() { - UNIMPLEMENTED + BMidiRosterLooper* looper = MidiRoster()->looper; + if (looper->Lock()) + { + looper->StopWatching(); + looper->Unlock(); + } } //------------------------------------------------------------------------------ -status_t BMidiRoster::Register(BMidiEndpoint* object) +status_t BMidiRoster::Register(BMidiEndpoint* endp) { - UNIMPLEMENTED - return B_ERROR; + if (endp != NULL) + { + return endp->Register(); + } + + return B_BAD_VALUE; } //------------------------------------------------------------------------------ -status_t BMidiRoster::Unregister(BMidiEndpoint* object) +status_t BMidiRoster::Unregister(BMidiEndpoint* endp) { - UNIMPLEMENTED - return B_ERROR; + if (endp != NULL) + { + return endp->Unregister(); + } + + return B_BAD_VALUE; } //------------------------------------------------------------------------------ BMidiRoster* BMidiRoster::MidiRoster() { - UNIMPLEMENTED - return NULL; + if (roster == NULL) + { + new BMidiRoster(); + } + + return roster; } //------------------------------------------------------------------------------ -BMidiRoster::BMidiRoster(BMessenger* remote) +BMidiRoster::BMidiRoster() { - UNIMPLEMENTED + TRACE(("BMidiRoster::BMidiRoster")) + + // While our constructor is executing, some function may + // call MidiRoster() again, which causes an endless loop. + // To prevent this, we immediately fill in "roster"; now + // subsequent calls to MidiRoster() won't mess up things. + + roster = this; + + server = new BMessenger(MIDI_SERVER_SIGNATURE); + looper = new BMidiRosterLooper(); + + if (!looper->Init(this)) { return; } + + BMessage msg; + msg.what = MSG_REGISTER_APP; + msg.AddMessenger("midi:messenger", BMessenger(looper)); + + if (server->SendMessage(&msg, looper, TIMEOUT) != B_OK) + { + WARN("Cannot send request to midi_server"); + return; + } + + // Although unlikely, we may receive the midi_server's + // "app registered" reply before we lock the semaphore. + // In that case, BMidiRosterLooper's MessageReceived() + // will bump the semaphore count, and our acquire_sem() + // can grab the semaphore safely (without blocking). + + acquire_sem(looper->initLock); } //------------------------------------------------------------------------------ BMidiRoster::~BMidiRoster() { - UNIMPLEMENTED + TRACE(("BMidiRoster::~BMidiRoster")) + + if (looper != NULL) + { + looper->Lock(); + looper->Quit(); + } + + delete server; } //------------------------------------------------------------------------------ @@ -121,172 +301,117 @@ void BMidiRoster::_Reserved8() { } //------------------------------------------------------------------------------ -status_t BMidiRoster::RemoteConnect( - int32 producer, int32 consumer, int32 port) +void BMidiRoster::CreateLocal(BMidiEndpoint* endp) { - UNIMPLEMENTED - return B_ERROR; + ASSERT(endp != NULL) + + // We are being called from the BMidiLocalConsumer or + // BMidiLocalProducer constructor, so there is no need + // to lock anything, because at this point there cannot + // be multiple threads accessing the endpoint's data. + + BMessage msg; + msg.what = MSG_CREATE_ENDPOINT; + msg.AddBool("midi:consumer", endp->isConsumer); + msg.AddBool("midi:registered", endp->isRegistered); + msg.AddString("midi:name", endp->Name()); + msg.AddMessage("midi:properties", endp->properties); + + if (endp->IsConsumer()) + { + BMidiConsumer* consumer = (BMidiConsumer*) endp; + msg.AddInt32("midi:port", consumer->port); + msg.AddInt64("midi:latency", consumer->latency); + } + + BMessage reply; + if (SendRequest(&msg, &reply) == B_OK) + { + status_t res; + if (reply.FindInt32("midi:result", &res) == B_OK) + { + if (res == B_OK) + { + int32 id; + if (reply.FindInt32("midi:id", &id) == B_OK) + { + endp->id = id; + + if (looper->Lock()) + { + looper->AddEndpoint(endp); + looper->Unlock(); + } + } + } + } + } + + // There are many things that can go wrong when creating + // a new endpoint, but BMidiEndpoint has no InitCheck() + // method to check for this. (You can, however, see if the + // endpoint's ID is 0 after the constructor returns, or + // call IsValid().) In any case, you should still Release() + // the endpoint to delete the object. (This is different + // from Be's implementation, which bumps the refcount only + // when creation succeeded. If you call Release(), you'll + // trip an assertion, so you can't delete these endpoints.) } //------------------------------------------------------------------------------ -status_t BMidiRoster::RemoteDisconnect(int32 producer, int32 consumer) +void BMidiRoster::DeleteLocal(BMidiEndpoint* endp) { - UNIMPLEMENTED - return B_ERROR; + ASSERT(endp != NULL) + + BMessage msg; + msg.what = MSG_DELETE_ENDPOINT; + msg.AddInt32("midi:id", endp->ID()); + + // Note: this is always called from BMidiLocalConsumer's + // or BMidiLocalProducer's destructor, so we don't expect + // a reply from the server. If something went wrong, the + // object will be destroyed regardless. + + server->SendMessage(&msg, (BHandler*) NULL, TIMEOUT); + + // If the endpoint was successfully created, we must remove + // it from the list of endpoints. If creation failed, then + // we didn't put the endpoint on that list. If the endpoint + // was connected to anything, we must also disconnect it. + + if (endp->ID() > 0) + { + if (looper->Lock()) + { + looper->RemoveEndpoint(endp); + looper->Unlock(); + } + } } //------------------------------------------------------------------------------ -void BMidiRoster::RemoteConnected(int32 producer, int32 consumer) +status_t BMidiRoster::SendRequest(BMessage* msg, BMessage* reply) { - UNIMPLEMENTED + ASSERT(msg != NULL) + ASSERT(reply != NULL) + + status_t err = server->SendMessage(msg, reply, TIMEOUT, TIMEOUT); + + if (err != B_OK) + { + WARN("Cannot send request to midi_server"); + } + + #ifdef DEBUG + if (err == B_OK) + { + printf("REPLY "); reply->PrintToStream(); + } + #endif + + return err; } //------------------------------------------------------------------------------ - -void BMidiRoster::RemoteDisconnected(int32 producer, int32 consumer) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -BMidiEndpoint* BMidiRoster::RemoteCreateProducer( - int32 producer, const char* name) -{ - UNIMPLEMENTED - return NULL; -} - -//------------------------------------------------------------------------------ - -BMidiEndpoint* BMidiRoster::RemoteCreateConsumer( - int32 consumer, const char* name, int32 port, int32 latency) -{ - UNIMPLEMENTED - return NULL; -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::RemoteDelete(BMidiEndpoint* object) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::RemoteRename( - BMidiEndpoint* object, const char* name) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::RemoteChangeLatency( - BMidiEndpoint* object, bigtime_t latency) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::Remote(int32 id, int32 op) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::DoRemote(BMessage* msg, BMessage* result) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::Rename(BMidiEndpoint* midi, const char* name) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::Release(BMidiEndpoint* midi) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::Acquire(BMidiEndpoint* midi) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::Create(BMidiEndpoint* midi) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -void BMidiRoster::SetLatency(BMidiConsumer* midi, bigtime_t latency) -{ - UNIMPLEMENTED -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::SetProperties( - BMidiEndpoint* midi, const BMessage* props) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::GetProperties( - const BMidiEndpoint* midi, BMessage* props) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::Connect( - BMidiProducer* source, BMidiConsumer* sink) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -status_t BMidiRoster::Disconnect( - BMidiProducer* source, BMidiConsumer* sink) -{ - UNIMPLEMENTED - return B_ERROR; -} - -//------------------------------------------------------------------------------ - -BLooper* BMidiRoster::Looper() -{ - UNIMPLEMENTED - return NULL; -} - -//------------------------------------------------------------------------------ - diff --git a/src/kits/midi2/MidiRosterLooper.cpp b/src/kits/midi2/MidiRosterLooper.cpp new file mode 100644 index 0000000000..06257b11c6 --- /dev/null +++ b/src/kits/midi2/MidiRosterLooper.cpp @@ -0,0 +1,719 @@ +/** + * @file MidiRosterLooper.cpp + * + * Implementation of the BMidiRosterLooper class. + * + * @author Matthijs Hollemans + */ + +#include "debug.h" +#include "MidiConsumer.h" +#include "MidiProducer.h" +#include "MidiRoster.h" +#include "MidiRosterLooper.h" +#include "protocol.h" + +//------------------------------------------------------------------------------ + +BMidiRosterLooper::BMidiRosterLooper() +{ + initLock = -1; + roster = NULL; + watcher = NULL; +} + +//------------------------------------------------------------------------------ + +BMidiRosterLooper::~BMidiRosterLooper() +{ + StopWatching(); + + if (initLock >= B_OK) + { + delete_sem(initLock); + } + + // At this point, our list may still contain endpoints with a + // zero reference count. These objects are proxies for remote + // endpoints, so we can safely delete them. If the list also + // contains endpoints with a non-zero refcount (which can be + // either remote or local), we will output a warning message. + // It would have been better to jump into the debugger, but I + // did not want to risk breaking any (misbehaving) old apps. + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->refCount > 0) + { + fprintf( + stderr, "[midi] WARNING: Endpoint %ld (%p) has " + "not been Release()d properly (refcount = %ld)\n", + endp->ID(), endp, endp->refCount); + } + else + { + delete endp; + } + } +} + +//------------------------------------------------------------------------------ + +bool BMidiRosterLooper::Init(BMidiRoster* roster_) +{ + ASSERT(roster_ != NULL) + + roster = roster_; + + // We create a semaphore with a zero count. BMidiRoster's + // MidiRoster() method will try to acquire this semaphore, + // but blocks because the count is 0. When we receive the + // "app registered" message in our MessageReceived() hook, + // we release the semaphore and MidiRoster() will unblock. + + initLock = create_sem(0, NULL); + + if (initLock < B_OK) + { + WARN("Could not create semaphore") + return false; + } + + thread_id threadId = Run(); + + if (threadId < B_OK) + { + WARN("Could not start looper thread") + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +BMidiEndpoint* BMidiRosterLooper::NextEndpoint(int32* id) +{ + ASSERT(id != NULL) + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->ID() > *id) + { + if (endp->IsRemote() && endp->IsRegistered()) + { + *id = endp->ID(); + return endp; + } + } + } + + return NULL; +} + +//------------------------------------------------------------------------------ + +BMidiEndpoint* BMidiRosterLooper::FindEndpoint(int32 id) +{ + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->ID() == id) + { + return endp; + } + } + + return NULL; +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::AddEndpoint(BMidiEndpoint* endp) +{ + ASSERT(endp != NULL) + ASSERT(!endpoints.HasItem(endp)) + + // We store the endpoints sorted by ID, because that + // simplifies the implementation of NextEndpoint(). + // Although the midi_server assigns IDs in ascending + // order, we can't assume that the mNEW messages also + // are delivered in this order (mostly they will be). + + int32 t; + for (t = CountEndpoints(); t > 0; --t) + { + BMidiEndpoint* other = EndpointAt(t - 1); + if (endp->ID() > other->ID()) + { + break; + } + } + endpoints.AddItem(endp, t); + + #ifdef DEBUG + DumpEndpoints(); + #endif +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::RemoveEndpoint(BMidiEndpoint* endp) +{ + ASSERT(endp != NULL) + ASSERT(endpoints.HasItem(endp)) + + endpoints.RemoveItem(endp); + + if (endp->IsConsumer()) + { + DisconnectDeadConsumer((BMidiConsumer*) endp); + } + else + { + DisconnectDeadProducer((BMidiProducer*) endp); + } + + #ifdef DEBUG + DumpEndpoints(); + #endif +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::StartWatching(const BMessenger* watcher_) +{ + ASSERT(watcher_ != NULL) + + StopWatching(); + watcher = new BMessenger(*watcher_); + + AllEndpoints(); + AllConnections(); +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::StopWatching() +{ + delete watcher; + watcher = NULL; +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::MessageReceived(BMessage* msg) +{ + #ifdef DEBUG + printf("IN "); msg->PrintToStream(); + #endif + + switch (msg->what) + { + case MSG_APP_REGISTERED: OnAppRegistered(msg); break; + case MSG_ENDPOINT_CREATED: OnEndpointCreated(msg); break; + case MSG_ENDPOINT_DELETED: OnEndpointDeleted(msg); break; + case MSG_ENDPOINT_CHANGED: OnEndpointChanged(msg); break; + case MSG_ENDPOINTS_CONNECTED: OnConnectedDisconnected(msg); break; + case MSG_ENDPOINTS_DISCONNECTED: OnConnectedDisconnected(msg); break; + + default: super::MessageReceived(msg); break; + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::OnAppRegistered(BMessage* msg) +{ + release_sem(initLock); +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::OnEndpointCreated(BMessage* msg) +{ + int32 id; + bool isRegistered; + BString name; + BMessage properties; + bool isConsumer; + + if ((msg->FindInt32("midi:id", &id) == B_OK) + && (msg->FindBool("midi:registered", &isRegistered) == B_OK) + && (msg->FindString("midi:name", &name) == B_OK) + && (msg->FindMessage("midi:properties", &properties) == B_OK) + && (msg->FindBool("midi:consumer", &isConsumer) == B_OK)) + { + if (isConsumer) + { + int32 port; + bigtime_t latency; + + if ((msg->FindInt32("midi:port", &port) == B_OK) + && (msg->FindInt64("midi:latency", &latency) == B_OK)) + { + BMidiConsumer* cons = new BMidiConsumer(); + cons->name = name; + cons->id = id; + cons->isRegistered = isRegistered; + cons->port = port; + cons->latency = latency; + *(cons->properties) = properties; + AddEndpoint(cons); + return; + } + } + else // producer + { + BMidiProducer* prod = new BMidiProducer(); + prod->name = name; + prod->id = id; + prod->isRegistered = isRegistered; + *(prod->properties) = properties; + AddEndpoint(prod); + return; + } + } + + WARN("Could not create proxy for remote endpoint") +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::OnEndpointDeleted(BMessage* msg) +{ + int32 id; + if (msg->FindInt32("midi:id", &id) == B_OK) + { + BMidiEndpoint* endp = FindEndpoint(id); + if (endp != NULL) + { + RemoveEndpoint(endp); + + // If the client is watching, and the endpoint is + // registered remote, we need to let it know that + // the endpoint is now unregistered. + + if (endp->IsRemote() && endp->IsRegistered()) + { + if (watcher != NULL) + { + BMessage notify; + notify.AddInt32("be:op", B_MIDI_UNREGISTERED); + ChangeEvent(¬ify, endp); + } + } + + // If the proxy object for this endpoint is no + // longer being used, we can delete it. However, + // if the refcount is not zero, we must defer + // destruction until the client Release()'s the + // object. We clear the "isRegistered" flag to + // let the client know the object is now invalid. + + if (endp->refCount == 0) + { + delete endp; + } + else // still being used + { + endp->isRegistered = false; + endp->isAlive = false; + } + + return; + } + } + + WARN("Could not delete proxy for remote endpoint") +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::OnEndpointChanged(BMessage* msg) +{ + int32 id; + if (msg->FindInt32("midi:id", &id) == B_OK) + { + BMidiEndpoint* endp = FindEndpoint(id); + if ((endp != NULL) && endp->IsRemote()) + { + ChangeRegistered(msg, endp); + ChangeName(msg, endp); + ChangeProperties(msg, endp); + ChangeLatency(msg, endp); + + #ifdef DEBUG + DumpEndpoints(); + #endif + + return; + } + } + + WARN("Could not change endpoint attributes") +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::OnConnectedDisconnected(BMessage* msg) +{ + int32 prodId, consId; + if ((msg->FindInt32("midi:producer", &prodId) == B_OK) + && (msg->FindInt32("midi:consumer", &consId) == B_OK)) + { + BMidiEndpoint* endp1 = FindEndpoint(prodId); + BMidiEndpoint* endp2 = FindEndpoint(consId); + + if ((endp1 != NULL) && endp1->IsProducer()) + { + if ((endp2 != NULL) && endp2->IsConsumer()) + { + BMidiProducer* prod = (BMidiProducer*) endp1; + BMidiConsumer* cons = (BMidiConsumer*) endp2; + + bool mustConnect = (msg->what == MSG_ENDPOINTS_CONNECTED); + + if (mustConnect) + { + prod->ConnectionMade(cons); + } + else + { + prod->ConnectionBroken(cons); + } + + if (watcher != NULL) + { + ConnectionEvent(prod, cons, mustConnect); + } + + #ifdef DEBUG + DumpEndpoints(); + #endif + + return; + } + } + } + + WARN("Could not connect/disconnect endpoints") +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ChangeRegistered(BMessage* msg, BMidiEndpoint* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + bool isRegistered; + if (msg->FindBool("midi:registered", &isRegistered) == B_OK) + { + if (endp->isRegistered != isRegistered) + { + endp->isRegistered = isRegistered; + + if (watcher != NULL) + { + BMessage notify; + if (isRegistered) + { + notify.AddInt32("be:op", B_MIDI_REGISTERED); + } + else + { + notify.AddInt32("be:op", B_MIDI_UNREGISTERED); + } + ChangeEvent(¬ify, endp); + } + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ChangeName(BMessage* msg, BMidiEndpoint* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + BString name; + if (msg->FindString("midi:name", &name) == B_OK) + { + if (endp->name != name) + { + endp->name = name; + + if ((watcher != NULL) && endp->IsRegistered()) + { + BMessage notify; + notify.AddInt32("be:op", B_MIDI_CHANGED_NAME); + notify.AddString("be:name", name); + ChangeEvent(¬ify, endp); + } + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ChangeProperties(BMessage* msg, BMidiEndpoint* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + BMessage properties; + if (msg->FindMessage("midi:properties", &properties) == B_OK) + { + *(endp->properties) = properties; + + if ((watcher != NULL) && endp->IsRegistered()) + { + BMessage notify; + notify.AddInt32("be:op", B_MIDI_CHANGED_PROPERTIES); + notify.AddMessage("be:properties", &properties); + ChangeEvent(¬ify, endp); + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ChangeLatency(BMessage* msg, BMidiEndpoint* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + bigtime_t latency; + if (msg->FindInt64("midi:latency", &latency) == B_OK) + { + if (endp->IsConsumer()) + { + BMidiConsumer* cons = (BMidiConsumer*) endp; + if (cons->latency != latency) + { + cons->latency = latency; + + if ((watcher != NULL) && cons->IsRegistered()) + { + BMessage notify; + notify.AddInt32("be:op", B_MIDI_CHANGED_LATENCY); + notify.AddInt64("be:latency", latency); + ChangeEvent(¬ify, endp); + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::AllEndpoints() +{ + BMessage notify; + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->IsRemote() && endp->IsRegistered()) + { + notify.MakeEmpty(); + notify.AddInt32("be:op", B_MIDI_REGISTERED); + ChangeEvent(¬ify, endp); + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::AllConnections() +{ + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->IsRemote() && endp->IsRegistered()) + { + if (endp->IsProducer()) + { + BMidiProducer* prod = (BMidiProducer*) endp; + if (prod->LockProducer()) + { + for (int32 k = 0; k < prod->CountConsumers(); ++k) + { + ConnectionEvent(prod, prod->ConsumerAt(k), true); + } + prod->UnlockProducer(); + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ChangeEvent(BMessage* msg, BMidiEndpoint* endp) +{ + ASSERT(watcher != NULL) + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + msg->what = B_MIDI_EVENT; + msg->AddInt32("be:id", endp->ID()); + + if (endp->IsConsumer()) + { + msg->AddString("be:type", "consumer"); + } + else + { + msg->AddString("be:type", "producer"); + } + + watcher->SendMessage(msg); +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::ConnectionEvent( + BMidiProducer* prod, BMidiConsumer* cons, bool mustConnect) +{ + ASSERT(watcher != NULL) + ASSERT(prod != NULL) + ASSERT(cons != NULL) + + BMessage notify; + notify.what = B_MIDI_EVENT; + notify.AddInt32("be:producer", prod->ID()); + notify.AddInt32("be:consumer", cons->ID()); + + if (mustConnect) + { + notify.AddInt32("be:op", B_MIDI_CONNECTED); + } + else + { + notify.AddInt32("be:op", B_MIDI_DISCONNECTED); + } + + watcher->SendMessage(¬ify); +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::DisconnectDeadConsumer(BMidiConsumer* cons) +{ + ASSERT(cons != NULL) + + // Note: Rather than looping through each producer's list + // of connected consumers, we let ConnectionBroken() tell + // us whether the consumer really was connected. + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + if (endp->IsProducer()) + { + BMidiProducer* prod = (BMidiProducer*) endp; + if (prod->ConnectionBroken(cons)) + { + if (cons->IsRemote() && (watcher != NULL)) + { + ConnectionEvent(prod, cons, false); + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void BMidiRosterLooper::DisconnectDeadProducer(BMidiProducer* prod) +{ + ASSERT(prod != NULL) + + // We don't need to lock or remove the consumers from + // the producer's list of connections, because when this + // function is called, we're destroying the object. + + if (prod->IsRemote() && (watcher != NULL)) + { + for (int32 t = 0; t < prod->CountConsumers(); ++t) + { + ConnectionEvent(prod, prod->ConsumerAt(t), false); + } + } +} + +//------------------------------------------------------------------------------ + +int32 BMidiRosterLooper::CountEndpoints() +{ + return endpoints.CountItems(); +} + +//------------------------------------------------------------------------------ + +BMidiEndpoint* BMidiRosterLooper::EndpointAt(int32 index) +{ + ASSERT(index >= 0 && index < CountEndpoints()) + + return (BMidiEndpoint*) endpoints.ItemAt(index); +} + +//------------------------------------------------------------------------------ + +#ifdef DEBUG +void BMidiRosterLooper::DumpEndpoints() +{ + if (Lock()) + { + printf("*** START DumpEndpoints\n"); + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + BMidiEndpoint* endp = EndpointAt(t); + + printf("\tendpoint %ld (%p):\n", t, endp); + + printf( + "\t\tid %ld, name '%s', %s, %s, %s, %s, refcount %ld\n", + endp->ID(), endp->Name(), + endp->IsConsumer() ? "consumer" : "producer", + endp->IsRegistered() ? "registered" : "unregistered", + endp->IsLocal() ? "local" : "remote", + endp->IsValid() ? "valid" : "invalid", endp->refCount); + + printf("\t\tproperties: "); + endp->properties->PrintToStream(); + + if (endp->IsConsumer()) + { + BMidiConsumer* cons = (BMidiConsumer*) endp; + printf("\t\tport %ld, latency %Ld\n", + cons->port, cons->latency); + } + else + { + BMidiProducer* prod = (BMidiProducer*) endp; + if (prod->LockProducer()) + { + printf("\t\tconnections:\n"); + for (int32 k = 0; k < prod->CountConsumers(); ++k) + { + BMidiConsumer* cons = prod->ConsumerAt(k); + printf("\t\t\tid %ld (%p)\n", cons->ID(), cons); + } + prod->UnlockProducer(); + } + } + } + + printf("*** END DumpEndpoints\n"); + Unlock(); + } +} +#endif + +//------------------------------------------------------------------------------ diff --git a/src/kits/midi2/MidiRosterLooper.h b/src/kits/midi2/MidiRosterLooper.h new file mode 100644 index 0000000000..e99cdf6f9c --- /dev/null +++ b/src/kits/midi2/MidiRosterLooper.h @@ -0,0 +1,145 @@ +/** + * @file MidiRosterLooper.h + * + * @author Matthijs Hollemans + */ + +#ifndef MIDI_ROSTER_LOOPER_H +#define MIDI_ROSTER_LOOPER_H + +#include +#include + +class BMidiRoster; + +/** + * Receives messages from the midi_server on behalf of the + * BMidiRoster. Also keeps track of the list of endpoints. + */ +class BMidiRosterLooper : public BLooper +{ +public: + + BMidiRosterLooper(); + virtual ~BMidiRosterLooper(); + + /** Starts up the looper. */ + bool Init(BMidiRoster* roster); + + /** Does the work for BMidiRoster::NextEndpoint(). */ + BMidiEndpoint* NextEndpoint(int32* id); + + /** Finds an endpoint in our list of endpoints. */ + BMidiEndpoint* FindEndpoint(int32 id); + + /** Adds an endpoint to our list. */ + void AddEndpoint(BMidiEndpoint* endp); + + /** + * Removes an endpoint from our list. Throws away + * any connections that this endpoint is part of. + */ + void RemoveEndpoint(BMidiEndpoint* endp); + + /** + * Invoked when the client app wants to be kept informed + * about changes in the roster. From now on, we will send + * the messenger B_MIDI_EVENT notifications when something + * interesting happens. But first, we send a whole bunch + * of notifications about the registered remote endpoints, + * and the connections between them. + */ + void StartWatching(const BMessenger* watcher); + + /** + * From now on, we will no longer send notifications to + * the client when something interesting happens. + */ + void StopWatching(); + + virtual void MessageReceived(BMessage* msg); + +private: + + friend class BMidiRoster; + friend class BMidiProducer; + + typedef BLooper super; + + void OnAppRegistered(BMessage* msg); + void OnEndpointCreated(BMessage* msg); + void OnEndpointDeleted(BMessage* msg); + void OnEndpointChanged(BMessage* msg); + void OnConnectedDisconnected(BMessage* msg); + + void ChangeRegistered(BMessage* msg, BMidiEndpoint* endp); + void ChangeName(BMessage* msg, BMidiEndpoint* endp); + void ChangeProperties(BMessage* msg, BMidiEndpoint* endp); + void ChangeLatency(BMessage* msg, BMidiEndpoint* endp); + + /** + * Removes the consumer from the list of connections of + * all the producers it is connected to. Also sends out + * B_MIDI_EVENT "disconnected" notifications if the + * consumer is remote and the client is watching. + */ + void DisconnectDeadConsumer(BMidiConsumer* cons); + + /** + * Sends out B_MIDI_EVENT "disconnected" notifications + * if the producer is remote and the client is watching. + */ + void DisconnectDeadProducer(BMidiProducer* prod); + + /** + * Sends B_MIDI_EVENT notifications for all registered + * remote endpoints to the watcher. Used when the client + * calls StartWatching(). + */ + void AllEndpoints(); + + /** + * Sends B_MIDI_EVENT notifications for the connections + * between all registered remote endpoints to the watcher. + * Used when the client calls StartWatching(). + */ + void AllConnections(); + + /** + * Sends a B_MIDI_EVENT notification to the watcher + * when another application changes the attributes + * of one of its endpoints. + */ + void ChangeEvent(BMessage* msg, BMidiEndpoint* endp); + + /** + * Sends a B_MIDI_EVENT notification to the watcher + * when another application connects or disconnects + * the two endpoints. + */ + void ConnectionEvent( + BMidiProducer* prod, BMidiConsumer* cons, bool mustConnect); + + int32 CountEndpoints(); + BMidiEndpoint* EndpointAt(int32 index); + + BMidiRoster* roster; + + /** + * Makes sure BMidiRoster::MidiRoster() does not return + * until confirmation from the midi_server is received. + */ + sem_id initLock; + + /** The object we send B_MIDI_EVENT notifications to. */ + BMessenger* watcher; + + /** All the endpoints in the system, local and remote. */ + BList endpoints; + + #ifdef DEBUG + void DumpEndpoints(); + #endif +}; + +#endif // MIDI_ROSTER_LOOPER_H diff --git a/src/servers/midi/Jamfile b/src/servers/midi/Jamfile index 2b6b9999ab..42e051b3b6 100644 --- a/src/servers/midi/Jamfile +++ b/src/servers/midi/Jamfile @@ -8,6 +8,4 @@ Server midi_server : LinkSharedOSLibs midi_server : be - root ; - diff --git a/src/servers/midi/MidiServerApp.cpp b/src/servers/midi/MidiServerApp.cpp index e520907b08..07148d325d 100644 --- a/src/servers/midi/MidiServerApp.cpp +++ b/src/servers/midi/MidiServerApp.cpp @@ -1,27 +1,41 @@ /** * @file MidiServerApp.cpp * + * Implementation of the MidiServerApp class. + * * @author Matthijs Hollemans - * @author Jerome Leveque */ #include +#include "debug.h" #include "MidiServerApp.h" +#include "ServerDefs.h" +#include "protocol.h" //------------------------------------------------------------------------------ MidiServerApp::MidiServerApp() : BApplication(MIDI_SERVER_SIGNATURE) { - //roster = new BMidiRoster(); + TRACE(("Running OpenBeOS MIDI server")) + + nextId = 1; } //------------------------------------------------------------------------------ -MidiServerApp::~MidiServerApp(void) +MidiServerApp::~MidiServerApp() { - //delete endpoints; + for (int32 t = 0; t < CountApps(); ++t) + { + delete AppAt(t); + } + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + delete EndpointAt(t); + } } //------------------------------------------------------------------------------ @@ -38,45 +52,772 @@ void MidiServerApp::AboutRequested() void MidiServerApp::MessageReceived(BMessage* msg) { + #ifdef DEBUG + printf("IN "); msg->PrintToStream(); + #endif + + // About thread safety inside the midi_server: even though + // multiple apps may be sending requests to the server at + // the same time, the BLooper's thread handles passes them + // to our MessageReceived() one after the other, so there + // is no need to synchronize anything. + 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; } } //------------------------------------------------------------------------------ -/*int32 MidiServerApp::GetNextFreeID() +void MidiServerApp::OnRegisterApp(BMessage* msg) { - return nextFreeID++; -}*/ + TRACE(("MidiServerApp::OnRegisterApp")) -//------------------------------------------------------------------------------ + // We only send the "app registered" message upon success, + // so if anything goes wrong here, we do not let the app + // know about it, and we consider it unregistered. (Most + // likely, the app is dead. If not, it freezes forever + // in anticipation of a message that will never arrive.) -/*BMidiEndpoint* MidiServerApp::NextEndPoint(int32* id) -{ - int32 item = 0; - BMidiEndpoint *endpoint; + app_t* app = new app_t; - while ((endpoint = (BMidiEndpoint*) endpoints->ItemAt(item)) != NULL) + if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK) { - if (endpoint->ID() > *id) + if (SendAllEndpoints(app)) { - endpoint->Acquire(); - *id = endpoint->ID(); - return endpoint; + if (SendAllConnections(app)) + { + BMessage reply; + reply.what = MSG_APP_REGISTERED; + + if (SendNotification(app, &reply)) + { + apps.AddItem(app); + + #ifdef DEBUG + DumpApps(); + #endif + + return; + } + } } - item++; } - return NULL; -}*/ + + delete app; +} //------------------------------------------------------------------------------ -/*BMidiRoster* MidiServerApp::GetRoster() +void MidiServerApp::OnCreateEndpoint(BMessage* msg) { - return roster; -}*/ + TRACE(("MidiServerApp::OnCreateEndpoint")) + + status_t err; + + endpoint_t* endp = new endpoint_t; + + endp->app = WhichApp(msg); + if (endp->app == NULL) + { + err = B_ERROR; + } + else + { + err = B_BAD_VALUE; + + if ((msg->FindBool("midi:consumer", &endp->consumer) == B_OK) + && (msg->FindBool("midi:registered", &endp->registered) == B_OK) + && (msg->FindString("midi:name", &endp->name) == B_OK) + && (msg->FindMessage("midi:properties", &endp->properties) == B_OK)) + { + if (endp->consumer) + { + if ((msg->FindInt32("midi:port", &endp->port) == B_OK) + && (msg->FindInt64("midi:latency", &endp->latency) == B_OK)) + { + err = B_OK; + } + } + else + { + err = B_OK; + } + } + } + + BMessage reply; + + if (err == B_OK) + { + endp->id = nextId++; + reply.AddInt32("midi:id", endp->id); + } + + reply.AddInt32("midi:result", err); + + if (SendReply(endp->app, msg, &reply) && (err == B_OK)) + { + AddEndpoint(msg, endp); + } + else + { + delete endp; + } +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::OnDeleteEndpoint(BMessage* msg) +{ + TRACE(("MidiServerApp::OnDeleteEndpoint")) + + // Clients send the "delete endpoint" message from + // the BMidiEndpoint destructor, so there is no point + // sending a reply, because the endpoint object will + // be destroyed no matter what. + + app_t* app = WhichApp(msg); + if (app != NULL) + { + endpoint_t* endp = WhichEndpoint(msg, app); + if (endp != NULL) + { + RemoveEndpoint(app, endp); + } + } +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::OnPurgeEndpoint(BMessage* msg) +{ + TRACE(("MidiServerApp::OnPurgeEndpoint")) + + // This performs the same task as OnDeleteEndpoint(), + // except that this message was send by the midi_server + // itself, so we don't check that the app that made the + // request really is the owner of the endpoint. (But we + // _do_ check that the message came from the server.) + + if (!msg->IsSourceRemote()) + { + int32 id; + if (msg->FindInt32("midi:id", &id) == B_OK) + { + endpoint_t* endp = FindEndpoint(id); + if (endp != NULL) + { + RemoveEndpoint(NULL, endp); + } + } + } +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::OnChangeEndpoint(BMessage* msg) +{ + TRACE(("MidiServerApp::OnChangeEndpoint")) + + endpoint_t* endp; + status_t err; + + app_t* app = WhichApp(msg); + if (app == NULL) + { + err = B_ERROR; + } + else + { + endp = WhichEndpoint(msg, app); + if (endp == NULL) + { + err = B_BAD_VALUE; + } + else + { + err = B_OK; + } + } + + BMessage reply; + reply.AddInt32("midi:result", err); + + if (SendReply(app, msg, &reply) && (err == B_OK)) + { + TRACE(("Endpoint %ld (%p) changed", endp->id, endp)) + + BMessage notify; + notify.what = MSG_ENDPOINT_CHANGED; + notify.AddInt32("midi:id", endp->id); + + bool registered; + if (msg->FindBool("midi:registered", ®istered) == B_OK) + { + notify.AddBool("midi:registered", registered); + endp->registered = registered; + } + + BString name; + if (msg->FindString("midi:name", &name) == B_OK) + { + notify.AddString("midi:name", name); + endp->name = name; + } + + BMessage properties; + if (msg->FindMessage("midi:properties", &properties) == B_OK) + { + notify.AddMessage("midi:properties", &properties); + endp->properties = properties; + } + + bigtime_t latency; + if (msg->FindInt64("midi:latency", &latency) == B_OK) + { + notify.AddInt64("midi:latency", latency); + endp->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 err; + endpoint_t* prod; + endpoint_t* cons; + + app_t* app = WhichApp(msg); + if (app == NULL) + { + err = B_ERROR; + } + else + { + err = B_BAD_VALUE; + + int32 prodId, consId; + if ((msg->FindInt32("midi:producer", &prodId) == B_OK) + && (msg->FindInt32("midi:consumer", &consId) == B_OK)) + { + prod = FindEndpoint(prodId); + cons = FindEndpoint(consId); + + if ((prod != NULL) && !prod->consumer) + { + if ((cons != NULL) && cons->consumer) + { + // It is an error to connect two endpoints that + // are already connected, or to disconnect two + // endpoints that are not connected at all. + + if (mustConnect == prod->connections.HasItem(cons)) + { + err = B_ERROR; + } + else + { + err = B_OK; + } + } + } + } + } + + BMessage reply; + reply.AddInt32("midi:result", err); + + if (SendReply(app, msg, &reply) && (err == B_OK)) + { + if (mustConnect) + { + TRACE(("Connection made: %ld ---> %ld", prod->id, cons->id)) + + prod->connections.AddItem(cons); + } + else + { + TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id)) + + prod->connections.RemoveItem(cons); + } + + BMessage notify; + MakeConnectedNotification(¬ify, prod, cons, mustConnect); + NotifyAll(¬ify, app); + + #ifdef DEBUG + DumpEndpoints(); + #endif + } +} + +//------------------------------------------------------------------------------ + +bool MidiServerApp::SendAllEndpoints(app_t* app) +{ + ASSERT(app != NULL) + + BMessage notify; + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* endp = EndpointAt(t); + + MakeCreatedNotification(¬ify, endp); + + if (!SendNotification(app, ¬ify)) { return false; } + } + + return true; +} + +//------------------------------------------------------------------------------ + +bool MidiServerApp::SendAllConnections(app_t* app) +{ + ASSERT(app != NULL) + + BMessage notify; + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* prod = EndpointAt(t); + if (!prod->consumer) + { + for (int32 k = 0; k < CountConnections(prod); ++k) + { + endpoint_t* cons = ConnectionAt(prod, k); + + MakeConnectedNotification(¬ify, prod, cons, true); + + if (!SendNotification(app, ¬ify)) { return false; } + } + } + } + + return true; +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + ASSERT(!endpoints.HasItem(endp)) + + TRACE(("Endpoint %ld (%p) added", endp->id, endp)) + + endpoints.AddItem(endp); + + BMessage notify; + MakeCreatedNotification(¬ify, endp); + NotifyAll(¬ify, endp->app); + + #ifdef DEBUG + DumpEndpoints(); + #endif +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp) +{ + ASSERT(endp != NULL) + ASSERT(endpoints.HasItem(endp)) + + TRACE(("Endpoint %ld (%p) removed", endp->id, endp)) + + endpoints.RemoveItem(endp); + + if (endp->consumer) + { + DisconnectDeadConsumer(endp); + } + + BMessage notify; + notify.what = MSG_ENDPOINT_DELETED; + notify.AddInt32("midi:id", endp->id); + NotifyAll(¬ify, app); + + delete endp; + + #ifdef DEBUG + DumpEndpoints(); + #endif +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons) +{ + ASSERT(cons != NULL) + ASSERT(cons->consumer) + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* prod = EndpointAt(t); + if (!prod->consumer) + { + prod->connections.RemoveItem(cons); + } + } +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp) +{ + ASSERT(msg != NULL) + ASSERT(endp != NULL) + + msg->MakeEmpty(); + msg->what = MSG_ENDPOINT_CREATED; + msg->AddInt32("midi:id", endp->id); + msg->AddBool("midi:consumer", endp->consumer); + msg->AddBool("midi:registered", endp->registered); + msg->AddString("midi:name", endp->name); + msg->AddMessage("midi:properties", &endp->properties); + + if (endp->consumer) + { + msg->AddInt32("midi:port", endp->port); + msg->AddInt64("midi:latency", endp->latency); + } +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::MakeConnectedNotification( + BMessage* msg, endpoint_t* prod, endpoint_t* cons, bool mustConnect) +{ + ASSERT(msg != NULL) + ASSERT(prod != NULL) + ASSERT(cons != NULL) + ASSERT(!prod->consumer) + ASSERT(cons->consumer) + + msg->MakeEmpty(); + + if (mustConnect) + { + msg->what = MSG_ENDPOINTS_CONNECTED; + } + else + { + msg->what = MSG_ENDPOINTS_DISCONNECTED; + } + + msg->AddInt32("midi:producer", prod->id); + msg->AddInt32("midi:consumer", cons->id); +} + +//------------------------------------------------------------------------------ + +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 %ld is not registered", retadr.Team())) + + return NULL; +} + +//------------------------------------------------------------------------------ + +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* endp = FindEndpoint(id); + if ((endp != NULL) && (endp->app == app)) + { + return endp; + } + } + + TRACE(("Endpoint not found or wrong app")) + + return NULL; +} + +//------------------------------------------------------------------------------ + +endpoint_t* MidiServerApp::FindEndpoint(int32 id) +{ + if (id > 0) + { + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* endp = EndpointAt(t); + if (endp->id == id) + { + return endp; + } + } + } + + TRACE(("Endpoint %ld not found", id)) + + return 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) + { + if (!SendNotification(app, msg)) + { + delete (app_t*) apps.RemoveItem(t); + + #ifdef DEBUG + DumpApps(); + #endif + } + } + } +} + +//------------------------------------------------------------------------------ + +bool MidiServerApp::SendNotification(app_t* app, BMessage* msg) +{ + ASSERT(app != NULL) + ASSERT(msg != NULL) + + status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT); + + if (err != B_OK) + { + DeliveryError(app); + } + + return (err == B_OK); +} + +//------------------------------------------------------------------------------ + +bool MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply) +{ + ASSERT(msg != NULL) + ASSERT(reply != NULL) + + status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT); + + if ((err != B_OK) && (app != NULL)) + { + DeliveryError(app); + apps.RemoveItem(app); + delete app; + + #ifdef DEBUG + DumpApps(); + #endif + } + + return (err == B_OK); +} + +//------------------------------------------------------------------------------ + +void MidiServerApp::DeliveryError(app_t* app) +{ + ASSERT(app != NULL) + + // We cannot communicate with the app, so we assume it's + // dead. We need to remove its endpoints from the roster, + // but we cannot do that right away; removing endpoints + // triggers a bunch of new notifications and we don't want + // those to get in the way of the notifications we are + // currently sending out. Instead, we consider the death + // of an app as a separate event, and pretend that the + // now-dead app sent us delete requests for its endpoints. + + TRACE(("Delivery error; unregistering app (%p)", app)) + + BMessage msg; + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* endp = EndpointAt(t); + if (endp->app == app) + { + msg.MakeEmpty(); + msg.what = MSG_PURGE_ENDPOINT; + msg.AddInt32("midi:id", endp->id); + + // It is not safe to post a message to your own + // looper's message queue, because you risk a + // deadlock if the queue is full. The chance of + // that happening is fairly small, but just in + // case, we catch it with a timeout. Because this + // situation is so unlikely, I decided to simply + // forget about the whole "purge" message then. + + if (be_app_messenger.SendMessage( + &msg, (BHandler*) NULL, TIMEOUT) != B_OK) + { + WARN("Could not deliver purge message") + } + } + } +} + +//------------------------------------------------------------------------------ + +int32 MidiServerApp::CountApps() +{ + return apps.CountItems(); +} + +//------------------------------------------------------------------------------ + +app_t* MidiServerApp::AppAt(int32 index) +{ + ASSERT(index >= 0 && index < CountApps()) + + return (app_t*) apps.ItemAt(index); +} + +//------------------------------------------------------------------------------ + +int32 MidiServerApp::CountEndpoints() +{ + return endpoints.CountItems(); +} + +//------------------------------------------------------------------------------ + +endpoint_t* MidiServerApp::EndpointAt(int32 index) +{ + ASSERT(index >= 0 && index < CountEndpoints()) + + return (endpoint_t*) endpoints.ItemAt(index); +} + +//------------------------------------------------------------------------------ + +int32 MidiServerApp::CountConnections(endpoint_t* prod) +{ + ASSERT(prod != NULL) + ASSERT(!prod->consumer) + + return prod->connections.CountItems(); +} + +//------------------------------------------------------------------------------ + +endpoint_t* MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index) +{ + ASSERT(prod != NULL) + ASSERT(!prod->consumer) + ASSERT(index >= 0 && index < CountConnections(prod)) + + return (endpoint_t*) prod->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 %ld (%p): team %ld\n", t, app, app->messenger.Team()); + } + + printf("*** END DumpApps\n"); +} +#endif + +//------------------------------------------------------------------------------ + +#ifdef DEBUG +void MidiServerApp::DumpEndpoints() +{ + printf("*** START DumpEndpoints\n"); + + for (int32 t = 0; t < CountEndpoints(); ++t) + { + endpoint_t* endp = EndpointAt(t); + + printf("\tendpoint %ld (%p):\n", t, endp); + + printf("\t\tid %ld, name '%s', %s, %s, app %p\n", + endp->id, endp->name.String(), + endp->consumer ? "consumer" : "producer", + endp->registered ? "registered" : "unregistered", + endp->app); + + printf("\t\tproperties: "); endp->properties.PrintToStream(); + + if (endp->consumer) + { + printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency); + } + else + { + printf("\t\tconnections:\n"); + for (int32 k = 0; k < CountConnections(endp); ++k) + { + endpoint_t* cons = ConnectionAt(endp, k); + printf("\t\t\tid %ld (%p)\n", cons->id, cons); + } + } + } + + printf("*** END DumpEndpoints\n"); +} +#endif //------------------------------------------------------------------------------ @@ -88,4 +829,3 @@ int main() } //------------------------------------------------------------------------------ - diff --git a/src/servers/midi/MidiServerApp.h b/src/servers/midi/MidiServerApp.h index 63d618ab74..1d283941c9 100644 --- a/src/servers/midi/MidiServerApp.h +++ b/src/servers/midi/MidiServerApp.h @@ -2,41 +2,155 @@ * @file MidiServerApp.h * * @author Matthijs Hollemans - * @author Jerome Leveque */ #ifndef MIDI_SERVER_APP_H #define MIDI_SERVER_APP_H #include +#include -#define MIDI_SERVER_SIGNATURE "application/x-vnd.OpenBeOS-midi-server" +struct app_t; +struct endpoint_t; /** - * The BApplication that drives the midi_server. + * The heart of the midi_server. This BApplication subclass + * keeps the roster of endpoints and applications, processes + * incoming messages from libmidi2.so, and notifies the apps + * when something interesting happens. */ class MidiServerApp : public BApplication { public: + MidiServerApp(); virtual ~MidiServerApp(); virtual void AboutRequested(); virtual void MessageReceived(BMessage* msg); - //int32 GetNextFreeID(); - //BMidiEndpoint* NextEndPoint(int32* id); - //BMidiRoster* GetRoster(); - private: - /** Our superclass. */ typedef BApplication super; - //BList* endpoints; - //int32 nextFreeID; - //BMidiRoster* roster; + void OnRegisterApp(BMessage* msg); + void OnCreateEndpoint(BMessage* msg); + void OnDeleteEndpoint(BMessage* msg); + void OnPurgeEndpoint(BMessage* msg); + void OnChangeEndpoint(BMessage* msg); + void OnConnectDisconnect(BMessage* msg); + + /** + * Sends an app MSG_ENDPOINT_CREATED notifications for + * all current endpoints. Used when the app registers. + */ + bool SendAllEndpoints(app_t* app); + + /** + * Sends an app MSG_ENDPOINTS_CONNECTED notifications for + * all current connections. Used when the app registers. + */ + bool SendAllConnections(app_t* app); + + /** + * Adds the specified endpoint to the roster, and notifies + * all other applications about this event. + */ + void AddEndpoint(BMessage* msg, endpoint_t* endp); + + /** + * Removes an endpoint from the roster, and notifies all + * 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 RemoveEndpoint(app_t* app, endpoint_t* endp); + + /** + * Removes a consumer from the list of connections of + * all the producers it is connected to, just before + * we remove it from the roster. + */ + void DisconnectDeadConsumer(endpoint_t* cons); + + /** Fills up a MSG_ENDPOINT_CREATED message. */ + void MakeCreatedNotification(BMessage* msg, endpoint_t* endp); + + /** Fills up a MSG_ENDPOINTS_(DIS)CONNECTED message. */ + void MakeConnectedNotification( + BMessage* msg, endpoint_t* prod, endpoint_t* cons, bool mustConnect); + + /** + * Figures out which application a message came from. + * Returns NULL if the application is not registered. + */ + app_t* WhichApp(BMessage* msg); + + /** + * Looks at the "midi:id" field from a message, and returns + * 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* WhichEndpoint(BMessage* msg, app_t* app); + + /** + * Returns the endpoint with the specified ID, or + * NULL if no such endpoint exists on the roster. + */ + endpoint_t* FindEndpoint(int32 id); + + /** + * Sends notification messages to all registered apps, + * except to the application that triggered the event. + * The "except" app is allowed to be NULL. + */ + void NotifyAll(BMessage* msg, app_t* except); + + /** + * Sends a notification message to an application, which is + * not necessarily registered yet. Applications never reply + * to such notification messages. + */ + bool SendNotification(app_t* app, BMessage* msg); + + /** + * Sends a reply to a request made by an application. + * If "app" is NULL, the application is not registered + * (and the reply should contain an error code). + */ + bool SendReply(app_t* app, BMessage* msg, BMessage* reply); + + /** + * Removes an app and all of its endpoints from the roster + * 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 DeliveryError(app_t* app); + + int32 CountApps(); + app_t* AppAt(int32 index); + + int32 CountEndpoints(); + endpoint_t* EndpointAt(int32 index); + + int32 CountConnections(endpoint_t* prod); + endpoint_t* ConnectionAt(endpoint_t* prod, int32 index); + + /** The registered applications. */ + BList apps; + + /** All the endpoints in the system. */ + BList endpoints; + + /** The ID we will assign to the next new endpoint. */ + int32 nextId; + + #ifdef DEBUG + void DumpApps(); + void DumpEndpoints(); + #endif }; #endif // MIDI_SERVER_APP_H - diff --git a/src/servers/midi/ServerDefs.h b/src/servers/midi/ServerDefs.h new file mode 100644 index 0000000000..7a5a5e3ea8 --- /dev/null +++ b/src/servers/midi/ServerDefs.h @@ -0,0 +1,74 @@ +/** + * @file ServerDefs.h + * + * Additional data types used by MidiServerApp. + * + * @author Matthijs Hollemans + */ + +#ifndef MIDI_SERVER_DEFS_H +#define MIDI_SERVER_DEFS_H + +#include +#include +#include +#include + +/** Describes an application that registered with the midi_server. */ +struct app_t +{ + /** For sending notifications to the app. */ + BMessenger messenger; +}; + +/** + * Describes a MIDI endpoint. The endpoint_t structure is + * used to describe both consumer and producer endpoints. + */ +struct endpoint_t +{ +#ifdef DEBUG + endpoint_t() + { + app = (app_t*) 0xbaadc0de; + } +#endif + + /** The application that owns this endpoint. */ + app_t* app; + + /** + * The endpoint's system-wide ID, which is assigned + * by the midi_server when the endpoint is created. + */ + int32 id; + + /** Is this a consumer or producer endpoint? */ + bool consumer; + + /** Whether this endpoint is visible to all applications. */ + bool registered; + + /** The endpoint's human-readable name. */ + BString name; + + /** User-defined attributes. */ + BMessage properties; + + /** The port that accepts MIDI events (consumer only). */ + port_id port; + + /** + * How long it takes this endpoint to process incoming + * MIDI events (consumer only). + */ + bigtime_t latency; + + /** + * Which consumers this endpoint sprays outgoing MIDI + * events to (producer only). + */ + BList connections; +}; + +#endif // MIDI_SERVER_DEFS_H