Files
haiku-beta6/src/kits/midi2/MidiRosterLooper.cpp
T

737 lines
17 KiB
C++
Raw Normal View History

2003-03-02 11:48:26 +00:00
/*
* Copyright (c) 2002-2004 Matthijs Hollemans
2003-03-11 20:38:00 +00:00
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "debug.h"
#include "MidiConsumer.h"
#include "MidiProducer.h"
#include "MidiRoster.h"
#include "MidiRosterLooper.h"
#include "protocol.h"
using namespace BPrivate;
//------------------------------------------------------------------------------
BMidiRosterLooper::BMidiRosterLooper()
: BLooper("MidiRosterLooper")
{
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, "InitLock");
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(&notify, 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(&notify, 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(&notify, 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(&notify, 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(&notify, 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(&notify, 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(&notify);
}
//------------------------------------------------------------------------------
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
//------------------------------------------------------------------------------