1/*
2 * Copyright 2006, Haiku.
3 *
4 * Copyright (c) 2002-2004 Matthijs Hollemans
5 * Distributed under the terms of the MIT License.
6 *
7 * Authors:
8 *		Matthijs Hollemans
9 */
10
11#include "debug.h"
12#include <MidiConsumer.h>
13#include <MidiProducer.h>
14#include <MidiRoster.h>
15#include "MidiRosterLooper.h"
16#include "protocol.h"
17
18using namespace BPrivate;
19
20
21BMidiRosterLooper::BMidiRosterLooper()
22	: BLooper("MidiRosterLooper")
23{
24	fInitLock = -1;
25	fRoster = NULL;
26	fWatcher = NULL;
27}
28
29
30BMidiRosterLooper::~BMidiRosterLooper()
31{
32	StopWatching();
33
34	if (fInitLock >= B_OK) {
35		delete_sem(fInitLock);
36	}
37
38	// At this point, our list may still contain endpoints with a
39	// zero reference count. These objects are proxies for remote
40	// endpoints, so we can safely delete them. If the list also
41	// contains endpoints with a non-zero refcount (which can be
42	// either remote or local), we will output a warning message.
43	// It would have been better to jump into the debugger, but I
44	// did not want to risk breaking any (misbehaving) old apps.
45
46	for (int32 t = 0; t < CountEndpoints(); ++t) {
47		BMidiEndpoint* endp = EndpointAt(t);
48		if (endp->fRefCount > 0) {
49			fprintf(
50				stderr, "[midi] WARNING: Endpoint %" B_PRId32 " (%p) has "
51				"not been Release()d properly (refcount = %" B_PRId32 ")\n",
52				endp->ID(), endp, endp->fRefCount);
53		} else {
54			delete endp;
55		}
56	}
57}
58
59
60bool
61BMidiRosterLooper::Init(BMidiRoster* roster_)
62{
63	ASSERT(roster_ != NULL)
64
65	fRoster = roster_;
66
67	// We create a semaphore with a zero count. BMidiRoster's
68	// MidiRoster() method will try to acquire this semaphore,
69	// but blocks because the count is 0. When we receive the
70	// "app registered" message in our MessageReceived() hook,
71	// we release the semaphore and MidiRoster() will unblock.
72
73	fInitLock = create_sem(0, "InitLock");
74
75	if (fInitLock < B_OK) {
76		WARN("Could not create semaphore")
77		return false;
78	}
79
80	thread_id threadId = Run();
81
82	if (threadId < B_OK) {
83		WARN("Could not start looper thread")
84		return false;
85	}
86
87	return true;
88}
89
90
91BMidiEndpoint*
92BMidiRosterLooper::NextEndpoint(int32* id)
93{
94	ASSERT(id != NULL)
95
96	for (int32 t = 0; t < CountEndpoints(); ++t) {
97		BMidiEndpoint* endp = EndpointAt(t);
98		if (endp->ID() > *id) {
99			if (endp->IsRemote() && endp->IsRegistered()) {
100				*id = endp->ID();
101				return endp;
102			}
103		}
104	}
105
106	return NULL;
107}
108
109
110BMidiEndpoint*
111BMidiRosterLooper::FindEndpoint(int32 id)
112{
113	for (int32 t = 0; t < CountEndpoints(); ++t) {
114		BMidiEndpoint* endp = EndpointAt(t);
115		if (endp->ID() == id) {
116			return endp;
117		}
118	}
119
120	return NULL;
121}
122
123
124void
125BMidiRosterLooper::AddEndpoint(BMidiEndpoint* endp)
126{
127	ASSERT(endp != NULL)
128	ASSERT(!fEndpoints.HasItem(endp))
129
130	// We store the endpoints sorted by ID, because that
131	// simplifies the implementation of NextEndpoint().
132	// Although the midi_server assigns IDs in ascending
133	// order, we can't assume that the mNEW messages also
134	// are delivered in this order (mostly they will be).
135
136	int32 t;
137	for (t = CountEndpoints(); t > 0; --t) {
138		BMidiEndpoint* other = EndpointAt(t - 1);
139		if (endp->ID() > other->ID()) {
140			break;
141		}
142	}
143	fEndpoints.AddItem(endp, t);
144
145	#ifdef DEBUG
146	DumpEndpoints();
147	#endif
148}
149
150
151void
152BMidiRosterLooper::RemoveEndpoint(BMidiEndpoint* endp)
153{
154	ASSERT(endp != NULL)
155	ASSERT(fEndpoints.HasItem(endp))
156
157	fEndpoints.RemoveItem(endp);
158
159	if (endp->IsConsumer()) {
160		DisconnectDeadConsumer((BMidiConsumer*) endp);
161	} else {
162		DisconnectDeadProducer((BMidiProducer*) endp);
163	}
164
165	#ifdef DEBUG
166	DumpEndpoints();
167	#endif
168}
169
170
171void
172BMidiRosterLooper::StartWatching(const BMessenger* watcher_)
173{
174	ASSERT(watcher_ != NULL)
175
176	StopWatching();
177	fWatcher = new BMessenger(*watcher_);
178
179	AllEndpoints();
180	AllConnections();
181}
182
183
184void
185BMidiRosterLooper::StopWatching()
186{
187	delete fWatcher;
188	fWatcher = NULL;
189}
190
191
192void
193BMidiRosterLooper::MessageReceived(BMessage* msg)
194{
195	#ifdef DEBUG
196	printf("IN "); msg->PrintToStream();
197	#endif
198
199	switch (msg->what) {
200		case MSG_APP_REGISTERED:         OnAppRegistered(msg);         break;
201		case MSG_ENDPOINT_CREATED:       OnEndpointCreated(msg);       break;
202		case MSG_ENDPOINT_DELETED:       OnEndpointDeleted(msg);       break;
203		case MSG_ENDPOINT_CHANGED:       OnEndpointChanged(msg);       break;
204		case MSG_ENDPOINTS_CONNECTED:    OnConnectedDisconnected(msg); break;
205		case MSG_ENDPOINTS_DISCONNECTED: OnConnectedDisconnected(msg); break;
206
207		default: super::MessageReceived(msg); break;
208	}
209}
210
211
212void
213BMidiRosterLooper::OnAppRegistered(BMessage* msg)
214{
215	release_sem(fInitLock);
216}
217
218
219void
220BMidiRosterLooper::OnEndpointCreated(BMessage* msg)
221{
222	int32 id;
223	bool isRegistered;
224	BString name;
225	BMessage properties;
226	bool isConsumer;
227
228	if ((msg->FindInt32("midi:id", &id) == B_OK)
229		&&  (msg->FindBool("midi:registered", &isRegistered) == B_OK)
230		&&  (msg->FindString("midi:name", &name) == B_OK)
231		&&  (msg->FindMessage("midi:properties", &properties) == B_OK)
232		&&  (msg->FindBool("midi:consumer", &isConsumer) == B_OK)) {
233		if (isConsumer) {
234			int32 port;
235			bigtime_t latency;
236
237			if ((msg->FindInt32("midi:port", &port) == B_OK)
238				&&  (msg->FindInt64("midi:latency", &latency) == B_OK)) {
239				BMidiConsumer* cons = new BMidiConsumer();
240				cons->fName          = name;
241				cons->fId            = id;
242				cons->fIsRegistered  = isRegistered;
243				cons->fPort          = port;
244				cons->fLatency       = latency;
245				*(cons->fProperties) = properties;
246				AddEndpoint(cons);
247				return;
248			}
249		} else { // producer
250			BMidiProducer* prod = new BMidiProducer();
251			prod->fName          = name;
252			prod->fId            = id;
253			prod->fIsRegistered  = isRegistered;
254			*(prod->fProperties) = properties;
255			AddEndpoint(prod);
256			return;
257		}
258	}
259
260	WARN("Could not create proxy for remote endpoint")
261}
262
263
264void
265BMidiRosterLooper::OnEndpointDeleted(BMessage* msg)
266{
267	int32 id;
268	if (msg->FindInt32("midi:id", &id) == B_OK) {
269		BMidiEndpoint* endp = FindEndpoint(id);
270		if (endp != NULL) {
271			RemoveEndpoint(endp);
272
273			// If the client is watching, and the endpoint is
274			// registered remote, we need to let it know that
275			// the endpoint is now unregistered.
276
277			if (endp->IsRemote() && endp->IsRegistered()) {
278				if (fWatcher != NULL) {
279					BMessage notify;
280					notify.AddInt32("be:op", B_MIDI_UNREGISTERED);
281					ChangeEvent(&notify, endp);
282				}
283			}
284
285			// If the proxy object for this endpoint is no
286			// longer being used, we can delete it. However,
287			// if the refcount is not zero, we must defer
288			// destruction until the client Release()'s the
289			// object. We clear the "isRegistered" flag to
290			// let the client know the object is now invalid.
291
292			if (endp->fRefCount == 0) {
293				delete endp;
294			} else { // still being used
295				endp->fIsRegistered = false;
296				endp->fIsAlive = false;
297			}
298
299			return;
300		}
301	}
302
303	WARN("Could not delete proxy for remote endpoint")
304}
305
306
307void
308BMidiRosterLooper::OnEndpointChanged(BMessage* msg)
309{
310	int32 id;
311	if (msg->FindInt32("midi:id", &id) == B_OK) {
312		BMidiEndpoint* endp = FindEndpoint(id);
313		if ((endp != NULL) && endp->IsRemote()) {
314			ChangeRegistered(msg, endp);
315			ChangeName(msg, endp);
316			ChangeProperties(msg, endp);
317			ChangeLatency(msg, endp);
318
319			#ifdef DEBUG
320			DumpEndpoints();
321			#endif
322
323			return;
324		}
325	}
326
327	WARN("Could not change endpoint attributes")
328}
329
330
331void
332BMidiRosterLooper::OnConnectedDisconnected(BMessage* msg)
333{
334	int32 prodId, consId;
335	if ((msg->FindInt32("midi:producer", &prodId) == B_OK)
336		&&  (msg->FindInt32("midi:consumer", &consId) == B_OK)) {
337		BMidiEndpoint* endp1 = FindEndpoint(prodId);
338		BMidiEndpoint* endp2 = FindEndpoint(consId);
339
340		if ((endp1 != NULL) && endp1->IsProducer()) {
341			if ((endp2 != NULL) && endp2->IsConsumer()) {
342				BMidiProducer* prod = (BMidiProducer*) endp1;
343				BMidiConsumer* cons = (BMidiConsumer*) endp2;
344
345				bool mustConnect = (msg->what == MSG_ENDPOINTS_CONNECTED);
346
347				if (mustConnect) {
348					prod->ConnectionMade(cons);
349				} else {
350					prod->ConnectionBroken(cons);
351				}
352
353				if (fWatcher != NULL) {
354					ConnectionEvent(prod, cons, mustConnect);
355				}
356
357				#ifdef DEBUG
358				DumpEndpoints();
359				#endif
360
361				return;
362			}
363		}
364	}
365
366	WARN("Could not connect/disconnect endpoints")
367}
368
369
370void
371BMidiRosterLooper::ChangeRegistered(BMessage* msg, BMidiEndpoint* endp)
372{
373	ASSERT(msg != NULL)
374	ASSERT(endp != NULL)
375
376	bool isRegistered;
377	if (msg->FindBool("midi:registered", &isRegistered) == B_OK) {
378		if (endp->fIsRegistered != isRegistered) {
379			endp->fIsRegistered = isRegistered;
380
381			if (fWatcher != NULL) {
382				BMessage notify;
383				if (isRegistered) {
384					notify.AddInt32("be:op", B_MIDI_REGISTERED);
385				} else {
386					notify.AddInt32("be:op", B_MIDI_UNREGISTERED);
387				}
388				ChangeEvent(&notify, endp);
389			}
390		}
391	}
392}
393
394
395void
396BMidiRosterLooper::ChangeName(BMessage* msg, BMidiEndpoint* endp)
397{
398	ASSERT(msg != NULL)
399	ASSERT(endp != NULL)
400
401	BString name;
402	if (msg->FindString("midi:name", &name) == B_OK) {
403		if (endp->fName != name) {
404			endp->fName = name;
405
406			if ((fWatcher != NULL) && endp->IsRegistered()) {
407				BMessage notify;
408				notify.AddInt32("be:op", B_MIDI_CHANGED_NAME);
409				notify.AddString("be:name", name);
410				ChangeEvent(&notify, endp);
411			}
412		}
413	}
414}
415
416
417void
418BMidiRosterLooper::ChangeProperties(BMessage* msg, BMidiEndpoint* endp)
419{
420	ASSERT(msg != NULL)
421	ASSERT(endp != NULL)
422
423	BMessage properties;
424	if (msg->FindMessage("midi:properties", &properties) == B_OK) {
425		*(endp->fProperties) = properties;
426
427		if ((fWatcher != NULL) && endp->IsRegistered()) {
428			BMessage notify;
429			notify.AddInt32("be:op", B_MIDI_CHANGED_PROPERTIES);
430			notify.AddMessage("be:properties", &properties);
431			ChangeEvent(&notify, endp);
432		}
433	}
434}
435
436
437void
438BMidiRosterLooper::ChangeLatency(BMessage* msg, BMidiEndpoint* endp)
439{
440	ASSERT(msg != NULL)
441	ASSERT(endp != NULL)
442
443	bigtime_t latency;
444	if (msg->FindInt64("midi:latency", &latency) == B_OK) {
445		if (endp->IsConsumer()) {
446			BMidiConsumer* cons = (BMidiConsumer*) endp;
447			if (cons->fLatency != latency) {
448				cons->fLatency = latency;
449
450				if ((fWatcher != NULL) && cons->IsRegistered()) {
451					BMessage notify;
452					notify.AddInt32("be:op", B_MIDI_CHANGED_LATENCY);
453					notify.AddInt64("be:latency", latency);
454					ChangeEvent(&notify, endp);
455				}
456			}
457		}
458	}
459}
460
461
462void
463BMidiRosterLooper::AllEndpoints()
464{
465	BMessage notify;
466	for (int32 t = 0; t < CountEndpoints(); ++t) {
467		BMidiEndpoint* endp = EndpointAt(t);
468		if (endp->IsRemote() && endp->IsRegistered()) {
469			notify.MakeEmpty();
470			notify.AddInt32("be:op", B_MIDI_REGISTERED);
471			ChangeEvent(&notify, endp);
472		}
473	}
474}
475
476
477void
478BMidiRosterLooper::AllConnections()
479{
480	for (int32 t = 0; t < CountEndpoints(); ++t) {
481		BMidiEndpoint* endp = EndpointAt(t);
482		if (endp->IsRemote() && endp->IsRegistered()) {
483			if (endp->IsProducer()) {
484				BMidiProducer* prod = (BMidiProducer*) endp;
485				if (prod->LockProducer()) {
486					for (int32 k = 0; k < prod->CountConsumers(); ++k) {
487						ConnectionEvent(prod, prod->ConsumerAt(k), true);
488					}
489					prod->UnlockProducer();
490				}
491			}
492		}
493	}
494}
495
496
497void
498BMidiRosterLooper::ChangeEvent(BMessage* msg, BMidiEndpoint* endp)
499{
500	ASSERT(fWatcher != NULL)
501	ASSERT(msg != NULL)
502	ASSERT(endp != NULL)
503
504	msg->what = B_MIDI_EVENT;
505	msg->AddInt32("be:id", endp->ID());
506
507	if (endp->IsConsumer()) {
508		msg->AddString("be:type", "consumer");
509	} else {
510		msg->AddString("be:type", "producer");
511	}
512
513	fWatcher->SendMessage(msg);
514}
515
516
517void
518BMidiRosterLooper::ConnectionEvent(
519	BMidiProducer* prod, BMidiConsumer* cons, bool mustConnect)
520{
521	ASSERT(fWatcher != NULL)
522	ASSERT(prod != NULL)
523	ASSERT(cons != NULL)
524
525	BMessage notify;
526	notify.what = B_MIDI_EVENT;
527	notify.AddInt32("be:producer", prod->ID());
528	notify.AddInt32("be:consumer", cons->ID());
529
530	if (mustConnect) {
531		notify.AddInt32("be:op", B_MIDI_CONNECTED);
532	} else {
533		notify.AddInt32("be:op", B_MIDI_DISCONNECTED);
534	}
535
536	fWatcher->SendMessage(&notify);
537}
538
539
540void
541BMidiRosterLooper::DisconnectDeadConsumer(BMidiConsumer* cons)
542{
543	ASSERT(cons != NULL)
544
545	// Note: Rather than looping through each producer's list
546	// of connected consumers, we let ConnectionBroken() tell
547	// us whether the consumer really was connected.
548
549	for (int32 t = 0; t < CountEndpoints(); ++t) {
550		BMidiEndpoint* endp = EndpointAt(t);
551		if (endp->IsProducer()) {
552			BMidiProducer* prod = (BMidiProducer*) endp;
553			if (prod->ConnectionBroken(cons)) {
554				if (cons->IsRemote() && (fWatcher != NULL)) {
555					ConnectionEvent(prod, cons, false);
556				}
557			}
558		}
559	}
560}
561
562
563void
564BMidiRosterLooper::DisconnectDeadProducer(BMidiProducer* prod)
565{
566	ASSERT(prod != NULL)
567
568	// We don't need to lock or remove the consumers from
569	// the producer's list of connections, because when this
570	// function is called, we're destroying the object.
571
572	if (prod->IsRemote() && (fWatcher != NULL)) {
573		for (int32 t = 0; t < prod->CountConsumers(); ++t) {
574			ConnectionEvent(prod, prod->ConsumerAt(t), false);
575		}
576	}
577}
578
579
580int32
581BMidiRosterLooper::CountEndpoints()
582{
583	return fEndpoints.CountItems();
584}
585
586
587BMidiEndpoint*
588BMidiRosterLooper::EndpointAt(int32 index)
589{
590	ASSERT(index >= 0 && index < CountEndpoints())
591
592	return (BMidiEndpoint*) fEndpoints.ItemAt(index);
593}
594
595
596#ifdef DEBUG
597void
598BMidiRosterLooper::DumpEndpoints()
599{
600	if (Lock()) {
601		printf("*** START DumpEndpoints\n");
602
603		for (int32 t = 0; t < CountEndpoints(); ++t) {
604			BMidiEndpoint* endp = EndpointAt(t);
605
606			printf("\tendpoint %" B_PRId32 " (%p):\n", t, endp);
607
608			printf(
609				"\t\tid %" B_PRId32 ", name '%s', %s, %s, %s, %s, refcount %"
610				B_PRId32 "\n", endp->ID(), endp->Name(),
611				endp->IsConsumer() ? "consumer" : "producer",
612				endp->IsRegistered() ? "registered" : "unregistered",
613				endp->IsLocal() ? "local" : "remote",
614				endp->IsValid() ? "valid" : "invalid", endp->fRefCount);
615
616			printf("\t\tproperties: ");
617			endp->fProperties->PrintToStream();
618
619			if (endp->IsConsumer()) {
620				BMidiConsumer* cons = (BMidiConsumer*) endp;
621				printf("\t\tport %" B_PRId32 ", latency %" B_PRIdBIGTIME "\n",
622						cons->fPort, cons->fLatency);
623			} else {
624				BMidiProducer* prod = (BMidiProducer*) endp;
625				if (prod->LockProducer()) {
626					printf("\t\tconnections:\n");
627					for (int32 k = 0; k < prod->CountConsumers(); ++k) {
628						BMidiConsumer* cons = prod->ConsumerAt(k);
629						printf("\t\t\tid %" B_PRId32 " (%p)\n", cons->ID(),
630							cons);
631					}
632					prod->UnlockProducer();
633				}
634			}
635		}
636
637		printf("*** END DumpEndpoints\n");
638		Unlock();
639	}
640}
641#endif
642
643