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