git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@17860 a95241bf-73f2-0310-859d-f6bbb57e9c96
640 lines
14 KiB
C++
640 lines
14 KiB
C++
/*
|
|
* Copyright 2002-2006, Haiku.
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Authors:
|
|
* Matthijs Hollemans
|
|
*/
|
|
|
|
#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")
|
|
{
|
|
fInitLock = -1;
|
|
fRoster = NULL;
|
|
fWatcher = NULL;
|
|
}
|
|
|
|
|
|
BMidiRosterLooper::~BMidiRosterLooper()
|
|
{
|
|
StopWatching();
|
|
|
|
if (fInitLock >= B_OK) {
|
|
delete_sem(fInitLock);
|
|
}
|
|
|
|
// 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->fRefCount > 0) {
|
|
fprintf(
|
|
stderr, "[midi] WARNING: Endpoint %ld (%p) has "
|
|
"not been Release()d properly (refcount = %ld)\n",
|
|
endp->ID(), endp, endp->fRefCount);
|
|
} else {
|
|
delete endp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
BMidiRosterLooper::Init(BMidiRoster* roster_)
|
|
{
|
|
ASSERT(roster_ != NULL)
|
|
|
|
fRoster = 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.
|
|
|
|
fInitLock = create_sem(0, "InitLock");
|
|
|
|
if (fInitLock < 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(!fEndpoints.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;
|
|
}
|
|
}
|
|
fEndpoints.AddItem(endp, t);
|
|
|
|
#ifdef DEBUG
|
|
DumpEndpoints();
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
BMidiRosterLooper::RemoveEndpoint(BMidiEndpoint* endp)
|
|
{
|
|
ASSERT(endp != NULL)
|
|
ASSERT(fEndpoints.HasItem(endp))
|
|
|
|
fEndpoints.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();
|
|
fWatcher = new BMessenger(*watcher_);
|
|
|
|
AllEndpoints();
|
|
AllConnections();
|
|
}
|
|
|
|
|
|
void
|
|
BMidiRosterLooper::StopWatching()
|
|
{
|
|
delete fWatcher;
|
|
fWatcher = 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(fInitLock);
|
|
}
|
|
|
|
|
|
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->fName = name;
|
|
cons->fId = id;
|
|
cons->fIsRegistered = isRegistered;
|
|
cons->fPort = port;
|
|
cons->fLatency = latency;
|
|
*(cons->fProperties) = properties;
|
|
AddEndpoint(cons);
|
|
return;
|
|
}
|
|
} else { // producer
|
|
BMidiProducer* prod = new BMidiProducer();
|
|
prod->fName = name;
|
|
prod->fId = id;
|
|
prod->fIsRegistered = isRegistered;
|
|
*(prod->fProperties) = 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 (fWatcher != 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->fRefCount == 0) {
|
|
delete endp;
|
|
} else { // still being used
|
|
endp->fIsRegistered = false;
|
|
endp->fIsAlive = 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 (fWatcher != 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->fIsRegistered != isRegistered) {
|
|
endp->fIsRegistered = isRegistered;
|
|
|
|
if (fWatcher != 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->fName != name) {
|
|
endp->fName = name;
|
|
|
|
if ((fWatcher != 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->fProperties) = properties;
|
|
|
|
if ((fWatcher != 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->fLatency != latency) {
|
|
cons->fLatency = latency;
|
|
|
|
if ((fWatcher != 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(fWatcher != 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");
|
|
}
|
|
|
|
fWatcher->SendMessage(msg);
|
|
}
|
|
|
|
|
|
void
|
|
BMidiRosterLooper::ConnectionEvent(
|
|
BMidiProducer* prod, BMidiConsumer* cons, bool mustConnect)
|
|
{
|
|
ASSERT(fWatcher != 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);
|
|
}
|
|
|
|
fWatcher->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() && (fWatcher != 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() && (fWatcher != NULL)) {
|
|
for (int32 t = 0; t < prod->CountConsumers(); ++t) {
|
|
ConnectionEvent(prod, prod->ConsumerAt(t), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int32
|
|
BMidiRosterLooper::CountEndpoints()
|
|
{
|
|
return fEndpoints.CountItems();
|
|
}
|
|
|
|
|
|
BMidiEndpoint*
|
|
BMidiRosterLooper::EndpointAt(int32 index)
|
|
{
|
|
ASSERT(index >= 0 && index < CountEndpoints())
|
|
|
|
return (BMidiEndpoint*) fEndpoints.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->fRefCount);
|
|
|
|
printf("\t\tproperties: ");
|
|
endp->fProperties->PrintToStream();
|
|
|
|
if (endp->IsConsumer()) {
|
|
BMidiConsumer* cons = (BMidiConsumer*) endp;
|
|
printf("\t\tport %ld, latency %Ld\n",
|
|
cons->fPort, cons->fLatency);
|
|
} 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
|
|
|