1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MIDIPlatformService.h"
8 #include "MIDIMessageQueue.h"
9 #include "TestMIDIPlatformService.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/StaticPtr.h"
12 #include "mozilla/Unused.h"
13 #include "mozilla/dom/MIDIManagerParent.h"
14 #include "mozilla/dom/MIDIPlatformRunnables.h"
15 #include "mozilla/dom/MIDIUtils.h"
16 #include "mozilla/dom/PMIDIManagerParent.h"
17 #include "mozilla/ipc/BackgroundParent.h"
18 #include "mozilla/dom/MIDIPortParent.h"
20 using namespace mozilla
;
21 using namespace mozilla::dom
;
23 MIDIPlatformService::MIDIPlatformService()
24 : mHasSentPortList(false),
25 mMessageQueueMutex("MIDIPlatformServce::mMessageQueueMutex") {}
27 MIDIPlatformService::~MIDIPlatformService() = default;
29 void MIDIPlatformService::CheckAndReceive(const nsAString
& aPortId
,
30 const nsTArray
<MIDIMessage
>& aMsgs
) {
31 ::mozilla::ipc::AssertIsOnBackgroundThread();
32 for (auto& port
: mPorts
) {
33 // TODO Clean this up when we split input/output port arrays
34 if (port
->MIDIPortInterface::Id() != aPortId
||
35 port
->Type() != MIDIPortType::Input
||
36 port
->ConnectionState() != MIDIPortConnectionState::Open
) {
39 if (!port
->SysexEnabled()) {
40 nsTArray
<MIDIMessage
> msgs
;
41 for (auto& msg
: aMsgs
) {
42 if (!MIDIUtils::IsSysexMessage(msg
)) {
43 msgs
.AppendElement(msg
);
46 Unused
<< port
->SendReceive(msgs
);
48 Unused
<< port
->SendReceive(aMsgs
);
53 void MIDIPlatformService::AddPort(MIDIPortParent
* aPort
) {
55 ::mozilla::ipc::AssertIsOnBackgroundThread();
56 mPorts
.AppendElement(aPort
);
59 void MIDIPlatformService::RemovePort(MIDIPortParent
* aPort
) {
60 // This should only be called from the background thread, when a MIDIPort
61 // actor has been destroyed.
62 ::mozilla::ipc::AssertIsOnBackgroundThread();
64 mPorts
.RemoveElement(aPort
);
68 void MIDIPlatformService::BroadcastState(const MIDIPortInfo
& aPortInfo
,
69 const MIDIPortDeviceState
& aState
) {
70 ::mozilla::ipc::AssertIsOnBackgroundThread();
71 for (auto& p
: mPorts
) {
72 if (p
->MIDIPortInterface::Id() == aPortInfo
.id() &&
73 p
->DeviceState() != aState
) {
74 p
->SendUpdateStatus(aState
, p
->ConnectionState());
79 void MIDIPlatformService::QueueMessages(const nsAString
& aId
,
80 nsTArray
<MIDIMessage
>& aMsgs
) {
81 ::mozilla::ipc::AssertIsOnBackgroundThread();
83 MutexAutoLock
lock(mMessageQueueMutex
);
84 MIDIMessageQueue
* msgQueue
= mMessageQueues
.GetOrInsertNew(aId
);
90 void MIDIPlatformService::SendPortList() {
91 ::mozilla::ipc::AssertIsOnBackgroundThread();
92 mHasSentPortList
= true;
94 for (auto& el
: mPortInfo
) {
95 l
.ports().AppendElement(el
);
97 for (auto& mgr
: mManagers
) {
98 Unused
<< mgr
->SendMIDIPortListUpdate(l
);
102 void MIDIPlatformService::Clear(MIDIPortParent
* aPort
) {
103 ::mozilla::ipc::AssertIsOnBackgroundThread();
106 MutexAutoLock
lock(mMessageQueueMutex
);
107 MIDIMessageQueue
* msgQueue
=
108 mMessageQueues
.Get(aPort
->MIDIPortInterface::Id());
115 void MIDIPlatformService::AddPortInfo(MIDIPortInfo
& aPortInfo
) {
116 ::mozilla::ipc::AssertIsOnBackgroundThread();
117 MOZ_ASSERT(XRE_IsParentProcess());
119 mPortInfo
.AppendElement(aPortInfo
);
121 // ORDER MATTERS HERE.
123 // When MIDI hardware is disconnected, all open MIDIPort objects revert to a
124 // "pending" state, and they are removed from the port maps of MIDIAccess
125 // objects. We need to send connection updates to all living ports first, THEN
126 // we can send port list updates to all of the live MIDIAccess objects. We
127 // have to go in this order because if a port object is still held live but is
128 // disconnected, it needs to readd itself to its originating MIDIAccess
129 // object. Running SendPortList first would cause MIDIAccess to create a new
130 // MIDIPort object, which would conflict (i.e. old disconnected object != new
131 // object in port map, which is against spec).
132 for (auto& port
: mPorts
) {
133 if (port
->MIDIPortInterface::Id() == aPortInfo
.id()) {
134 port
->SendUpdateStatus(MIDIPortDeviceState::Connected
,
135 port
->ConnectionState());
138 if (mHasSentPortList
) {
143 void MIDIPlatformService::RemovePortInfo(MIDIPortInfo
& aPortInfo
) {
144 ::mozilla::ipc::AssertIsOnBackgroundThread();
145 mPortInfo
.RemoveElement(aPortInfo
);
146 BroadcastState(aPortInfo
, MIDIPortDeviceState::Disconnected
);
147 if (mHasSentPortList
) {
152 StaticRefPtr
<MIDIPlatformService
> gMIDIPlatformService
;
155 bool MIDIPlatformService::IsRunning() {
156 return gMIDIPlatformService
!= nullptr;
159 void MIDIPlatformService::Close(mozilla::dom::MIDIPortParent
* aPort
) {
160 ::mozilla::ipc::AssertIsOnBackgroundThread();
162 MutexAutoLock
lock(mMessageQueueMutex
);
163 MIDIMessageQueue
* msgQueue
=
164 mMessageQueues
.Get(aPort
->MIDIPortInterface::Id());
166 msgQueue
->ClearAfterNow();
169 // Send all messages before sending a close request
170 ScheduleSend(aPort
->MIDIPortInterface::Id());
171 // TODO We should probably have the send function schedule closing
172 ScheduleClose(aPort
);
176 MIDIPlatformService
* MIDIPlatformService::Get() {
177 // We should never touch the platform service in a child process.
178 MOZ_ASSERT(XRE_IsParentProcess());
179 ::mozilla::ipc::AssertIsOnBackgroundThread();
182 // Uncomment once we have an actual platform library to test.
184 // bool useTestService = false;
185 // rv = Preferences::GetRootBranch()->GetBoolPref("midi.testing",
187 gMIDIPlatformService
= new TestMIDIPlatformService();
188 gMIDIPlatformService
->Init();
190 return gMIDIPlatformService
;
193 void MIDIPlatformService::MaybeStop() {
194 ::mozilla::ipc::AssertIsOnBackgroundThread();
196 // Service already stopped or never started. Exit.
199 // If we have any ports or managers left, we should still be alive.
200 if (!mPorts
.IsEmpty() || !mManagers
.IsEmpty()) {
204 gMIDIPlatformService
= nullptr;
207 void MIDIPlatformService::AddManager(MIDIManagerParent
* aManager
) {
208 ::mozilla::ipc::AssertIsOnBackgroundThread();
209 mManagers
.AppendElement(aManager
);
210 // Managers add themselves during construction. We have to wait for the
211 // protocol construction to finish before we send them a port list. The
212 // runnable calls SendPortList, which iterates through the live manager list,
213 // so this saves us from having to worry about Manager pointer validity at
214 // time of runnable execution.
215 nsCOMPtr
<nsIRunnable
> r(new SendPortListRunnable());
216 NS_DispatchToCurrentThread(r
);
219 void MIDIPlatformService::RemoveManager(MIDIManagerParent
* aManager
) {
220 ::mozilla::ipc::AssertIsOnBackgroundThread();
221 mManagers
.RemoveElement(aManager
);
225 void MIDIPlatformService::UpdateStatus(
226 const nsAString
& aPortId
, const MIDIPortDeviceState
& aDeviceState
,
227 const MIDIPortConnectionState
& aConnectionState
) {
228 ::mozilla::ipc::AssertIsOnBackgroundThread();
229 for (auto port
: mPorts
) {
230 if (port
->MIDIPortInterface::Id() == aPortId
) {
231 port
->SendUpdateStatus(aDeviceState
, aConnectionState
);
236 void MIDIPlatformService::GetMessages(const nsAString
& aPortId
,
237 nsTArray
<MIDIMessage
>& aMsgs
) {
238 // Can run on either background thread or platform specific IO Thread.
240 MutexAutoLock
lock(mMessageQueueMutex
);
241 MIDIMessageQueue
* msgQueue
;
242 if (!mMessageQueues
.Get(aPortId
, &msgQueue
)) {
245 msgQueue
->GetMessages(aMsgs
);
249 void MIDIPlatformService::GetMessagesBefore(const nsAString
& aPortId
,
250 const TimeStamp
& aTimeStamp
,
251 nsTArray
<MIDIMessage
>& aMsgs
) {
252 // Can run on either background thread or platform specific IO Thread.
254 MutexAutoLock
lock(mMessageQueueMutex
);
255 MIDIMessageQueue
* msgQueue
;
256 if (!mMessageQueues
.Get(aPortId
, &msgQueue
)) {
259 msgQueue
->GetMessagesBefore(aTimeStamp
, aMsgs
);