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