/* 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(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& aMsgs) : MIDIBackgroundRunnable("QueueMessagesRunnable"), mPortID(aPortID), mMsgs(aMsgs.Clone()) {} ~QueueMessagesRunnable() = default; virtual void RunInternal() { MIDIPlatformService::AssertThread(); MIDIPlatformService::Get()->QueueMessages(mPortID, mMsgs); } private: nsString mPortID; nsTArray 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(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(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(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(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(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 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 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 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 r(new ProcessMessagesRunnable(aPortId)); OwnerThread()->Dispatch(r.forget()); } void TestMIDIPlatformService::ProcessMessages(const nsAString& aPortId) { nsTArray 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 r( new ReceiveRunnable(mControlInputPort.id(), msg)); OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); break; } // Cause control test ports to connect case 0x01: { nsCOMPtr r1( new AddPortRunnable(mStateTestInputPort)); OwnerThread()->Dispatch(r1, NS_DISPATCH_NORMAL); break; } // Cause control test ports to disconnect case 0x02: { nsCOMPtr 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 newMsgs; nsTArray 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 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 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 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 newMsgs; MIDIUtils::ParseMessages(msgs, TimeStamp::Now(), newMsgs); nsCOMPtr r( new ReceiveRunnable(mControlInputPort.id(), newMsgs)); OwnerThread()->Dispatch(r, NS_DISPATCH_NORMAL); break; } default: NS_WARNING("Unknown Test Sysex MIDI message received!"); } break; } } } }