зеркало из https://github.com/mozilla/gecko-dev.git
259 строки
9.5 KiB
C++
259 строки
9.5 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "TestMIDIPlatformService.h"
|
|
#include "mozilla/dom/MIDIPort.h"
|
|
#include "mozilla/dom/MIDITypes.h"
|
|
#include "mozilla/dom/MIDIPortInterface.h"
|
|
#include "mozilla/dom/MIDIPortParent.h"
|
|
#include "mozilla/dom/MIDIPlatformRunnables.h"
|
|
#include "mozilla/dom/MIDIUtils.h"
|
|
#include "mozilla/ipc/BackgroundParent.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsIThread.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::ipc;
|
|
|
|
/**
|
|
* Runnable used for making sure ProcessMessages only happens on the IO thread.
|
|
*
|
|
*/
|
|
class ProcessMessagesRunnable : public mozilla::Runnable {
|
|
public:
|
|
explicit ProcessMessagesRunnable(const nsAString& aPortID)
|
|
: Runnable("ProcessMessagesRunnable"), mPortID(aPortID) {}
|
|
~ProcessMessagesRunnable() = default;
|
|
NS_IMETHOD Run() override {
|
|
// If service is no longer running, just exist without processing.
|
|
if (!MIDIPlatformService::IsRunning()) {
|
|
return NS_OK;
|
|
}
|
|
TestMIDIPlatformService* srv =
|
|
static_cast<TestMIDIPlatformService*>(MIDIPlatformService::Get());
|
|
srv->ProcessMessages(mPortID);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsString mPortID;
|
|
};
|
|
|
|
/**
|
|
* Runnable used for allowing IO thread to queue more messages for processing,
|
|
* since it can't access the service object directly.
|
|
*
|
|
*/
|
|
class QueueMessagesRunnable : public MIDIBackgroundRunnable {
|
|
public:
|
|
QueueMessagesRunnable(const nsAString& aPortID,
|
|
const nsTArray<MIDIMessage>& aMsgs)
|
|
: MIDIBackgroundRunnable("QueueMessagesRunnable"),
|
|
mPortID(aPortID),
|
|
mMsgs(aMsgs.Clone()) {}
|
|
~QueueMessagesRunnable() = default;
|
|
virtual void RunInternal() {
|
|
MIDIPlatformService::AssertThread();
|
|
MIDIPlatformService::Get()->QueueMessages(mPortID, mMsgs);
|
|
}
|
|
|
|
private:
|
|
nsString mPortID;
|
|
nsTArray<MIDIMessage> mMsgs;
|
|
};
|
|
|
|
TestMIDIPlatformService::TestMIDIPlatformService()
|
|
: mControlInputPort(u"b744eebe-f7d8-499b-872b-958f63c8f522"_ns,
|
|
u"Test Control MIDI Device Input Port"_ns,
|
|
u"Test Manufacturer"_ns, u"1.0.0"_ns,
|
|
static_cast<uint32_t>(MIDIPortType::Input)),
|
|
mControlOutputPort(u"ab8e7fe8-c4de-436a-a960-30898a7c9a3d"_ns,
|
|
u"Test Control MIDI Device Output Port"_ns,
|
|
u"Test Manufacturer"_ns, u"1.0.0"_ns,
|
|
static_cast<uint32_t>(MIDIPortType::Output)),
|
|
mStateTestInputPort(u"a9329677-8588-4460-a091-9d4a7f629a48"_ns,
|
|
u"Test State MIDI Device Input Port"_ns,
|
|
u"Test Manufacturer"_ns, u"1.0.0"_ns,
|
|
static_cast<uint32_t>(MIDIPortType::Input)),
|
|
mStateTestOutputPort(u"478fa225-b5fc-4fa6-a543-d32d9cb651e7"_ns,
|
|
u"Test State MIDI Device Output Port"_ns,
|
|
u"Test Manufacturer"_ns, u"1.0.0"_ns,
|
|
static_cast<uint32_t>(MIDIPortType::Output)),
|
|
mAlwaysClosedTestOutputPort(u"f87d0c76-3c68-49a9-a44f-700f1125c07a"_ns,
|
|
u"Always Closed MIDI Device Output Port"_ns,
|
|
u"Test Manufacturer"_ns, u"1.0.0"_ns,
|
|
static_cast<uint32_t>(MIDIPortType::Output)),
|
|
mDoRefresh(false),
|
|
mIsInitialized(false) {
|
|
MIDIPlatformService::AssertThread();
|
|
}
|
|
|
|
TestMIDIPlatformService::~TestMIDIPlatformService() {
|
|
MIDIPlatformService::AssertThread();
|
|
}
|
|
|
|
void TestMIDIPlatformService::Init() {
|
|
MIDIPlatformService::AssertThread();
|
|
|
|
if (mIsInitialized) {
|
|
return;
|
|
}
|
|
mIsInitialized = true;
|
|
|
|
// Treat all of our special ports as always connected. When the service comes
|
|
// up, prepopulate the port list with them.
|
|
MIDIPlatformService::Get()->AddPortInfo(mControlInputPort);
|
|
MIDIPlatformService::Get()->AddPortInfo(mControlOutputPort);
|
|
MIDIPlatformService::Get()->AddPortInfo(mAlwaysClosedTestOutputPort);
|
|
MIDIPlatformService::Get()->AddPortInfo(mStateTestOutputPort);
|
|
nsCOMPtr<nsIRunnable> r(new SendPortListRunnable());
|
|
|
|
// Start the IO Thread.
|
|
OwnerThread()->Dispatch(r.forget());
|
|
}
|
|
|
|
void TestMIDIPlatformService::Refresh() {
|
|
if (mDoRefresh) {
|
|
AddPortInfo(mStateTestInputPort);
|
|
mDoRefresh = false;
|
|
}
|
|
}
|
|
|
|
void TestMIDIPlatformService::Open(MIDIPortParent* aPort) {
|
|
MOZ_ASSERT(aPort);
|
|
MIDIPortConnectionState s = MIDIPortConnectionState::Open;
|
|
if (aPort->MIDIPortInterface::Id() == mAlwaysClosedTestOutputPort.id()) {
|
|
// If it's the always closed testing port, act like it's already opened
|
|
// exclusively elsewhere.
|
|
s = MIDIPortConnectionState::Closed;
|
|
}
|
|
// Connection events are just simulated on the background thread, no need to
|
|
// push to IO thread.
|
|
nsCOMPtr<nsIRunnable> r(
|
|
new SetStatusRunnable(aPort, aPort->DeviceState(), s));
|
|
OwnerThread()->Dispatch(r.forget());
|
|
}
|
|
|
|
void TestMIDIPlatformService::ScheduleClose(MIDIPortParent* aPort) {
|
|
AssertThread();
|
|
MOZ_ASSERT(aPort);
|
|
if (aPort->ConnectionState() == MIDIPortConnectionState::Open) {
|
|
// Connection events are just simulated on the background thread, no need to
|
|
// push to IO thread.
|
|
nsCOMPtr<nsIRunnable> r(new SetStatusRunnable(
|
|
aPort, aPort->DeviceState(), MIDIPortConnectionState::Closed));
|
|
OwnerThread()->Dispatch(r.forget());
|
|
}
|
|
}
|
|
|
|
void TestMIDIPlatformService::Stop() { MIDIPlatformService::AssertThread(); }
|
|
|
|
void TestMIDIPlatformService::ScheduleSend(const nsAString& aPortId) {
|
|
AssertThread();
|
|
nsCOMPtr<nsIRunnable> r(new ProcessMessagesRunnable(aPortId));
|
|
OwnerThread()->Dispatch(r.forget());
|
|
}
|
|
|
|
void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) {
|
|
nsTArray<MIDIMessage> msgs;
|
|
GetMessagesBefore(aPortId, TimeStamp::Now(), msgs);
|
|
|
|
for (MIDIMessage msg : msgs) {
|
|
// receiving message from test control port
|
|
if (aPortId == mControlOutputPort.id()) {
|
|
switch (msg.data()[0]) {
|
|
// Hit a note, get a test!
|
|
case 0x90:
|
|
switch (msg.data()[1]) {
|
|
// Echo data/timestamp back through output port
|
|
case 0x00: {
|
|
nsCOMPtr<nsIRunnable> r(
|
|
new ReceiveRunnable(mControlInputPort.id(), msg));
|
|
OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
// Cause control test ports to connect
|
|
case 0x01: {
|
|
nsCOMPtr<nsIRunnable> r1(
|
|
new AddPortRunnable(mStateTestInputPort));
|
|
OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
// Cause control test ports to disconnect
|
|
case 0x02: {
|
|
nsCOMPtr<nsIRunnable> r1(
|
|
new RemovePortRunnable(mStateTestInputPort));
|
|
OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
// Test for packet timing
|
|
case 0x03: {
|
|
// Append a few echo command packets in reverse timing order,
|
|
// should come out in correct order on other end.
|
|
nsTArray<MIDIMessage> newMsgs;
|
|
nsTArray<uint8_t> msg;
|
|
msg.AppendElement(0x90);
|
|
msg.AppendElement(0x00);
|
|
msg.AppendElement(0x00);
|
|
// PR_Now() returns nanosecods, and we need a double with
|
|
// fractional milliseconds.
|
|
TimeStamp currentTime = TimeStamp::Now();
|
|
for (int i = 0; i <= 5; ++i) {
|
|
// Insert messages with timestamps in reverse order, to make
|
|
// sure we're sorting correctly.
|
|
newMsgs.AppendElement(MIDIMessage(
|
|
msg, currentTime - TimeDuration::FromMilliseconds(i * 2)));
|
|
}
|
|
nsCOMPtr<nsIRunnable> r(
|
|
new QueueMessagesRunnable(aPortId, newMsgs));
|
|
OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
// Causes the next refresh to add new ports to the list
|
|
case 0x04: {
|
|
mDoRefresh = true;
|
|
break;
|
|
}
|
|
default:
|
|
NS_WARNING("Unknown Test MIDI message received!");
|
|
}
|
|
break;
|
|
// Sysex tests
|
|
case 0xF0:
|
|
switch (msg.data()[1]) {
|
|
// Echo data/timestamp back through output port
|
|
case 0x00: {
|
|
nsCOMPtr<nsIRunnable> r(
|
|
new ReceiveRunnable(mControlInputPort.id(), msg));
|
|
OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
// Test for system real time messages in the middle of sysex
|
|
// messages.
|
|
case 0x01: {
|
|
nsTArray<uint8_t> msgs;
|
|
const uint8_t msg[] = {0xF0, 0x01, 0xFA, 0x02, 0x03,
|
|
0x04, 0xF8, 0x05, 0xF7};
|
|
// Can't use AppendElements on an array here, so just do range
|
|
// based loading.
|
|
for (const auto& s : msg) {
|
|
msgs.AppendElement(s);
|
|
}
|
|
nsTArray<MIDIMessage> newMsgs;
|
|
MIDIUtils::ParseMessages(msgs, TimeStamp::Now(), newMsgs);
|
|
nsCOMPtr<nsIRunnable> r(
|
|
new ReceiveRunnable(mControlInputPort.id(), newMsgs));
|
|
OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL);
|
|
break;
|
|
}
|
|
default:
|
|
NS_WARNING("Unknown Test Sysex MIDI message received!");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|