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
This commit is contained in:
@@ -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
|
||||
;
|
||||
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
+256
-42
@@ -1,73 +1,110 @@
|
||||
/**
|
||||
* @file MidiEndpoint.cpp
|
||||
*
|
||||
* Implementation of the BMidiEndpoint class.
|
||||
*
|
||||
* @author Matthijs Hollemans
|
||||
* @author Jerome Leveque
|
||||
*/
|
||||
|
||||
#include <MidiRoster.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
+184
-35
@@ -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();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
+311
-186
@@ -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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @file MidiRosterLooper.h
|
||||
*
|
||||
* @author Matthijs Hollemans
|
||||
*/
|
||||
|
||||
#ifndef MIDI_ROSTER_LOOPER_H
|
||||
#define MIDI_ROSTER_LOOPER_H
|
||||
|
||||
#include <Looper.h>
|
||||
#include <Message.h>
|
||||
|
||||
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
|
||||
@@ -8,6 +8,4 @@ Server midi_server :
|
||||
|
||||
LinkSharedOSLibs midi_server :
|
||||
be
|
||||
root
|
||||
;
|
||||
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
/**
|
||||
* @file MidiServerApp.cpp
|
||||
*
|
||||
* Implementation of the MidiServerApp class.
|
||||
*
|
||||
* @author Matthijs Hollemans
|
||||
* @author Jerome Leveque
|
||||
*/
|
||||
|
||||
#include <Alert.h>
|
||||
|
||||
#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()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -2,41 +2,155 @@
|
||||
* @file MidiServerApp.h
|
||||
*
|
||||
* @author Matthijs Hollemans
|
||||
* @author Jerome Leveque
|
||||
*/
|
||||
|
||||
#ifndef MIDI_SERVER_APP_H
|
||||
#define MIDI_SERVER_APP_H
|
||||
|
||||
#include <Application.h>
|
||||
#include <List.h>
|
||||
|
||||
#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
|
||||
|
||||
|
||||
@@ -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 <List.h>
|
||||
#include <Message.h>
|
||||
#include <Messenger.h>
|
||||
#include <String.h>
|
||||
|
||||
/** 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
|
||||
Reference in New Issue
Block a user