xref: /haiku/src/kits/midi2/MidiRosterLooper.cpp (revision 1a7bcf6962e1c99906cce0fe602e08c3fcda46f6)
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 
18 using namespace BPrivate;
19 
20 
BMidiRosterLooper()21 BMidiRosterLooper::BMidiRosterLooper()
22 	: BLooper("MidiRosterLooper")
23 {
24 	fInitLock = -1;
25 	fRoster = NULL;
26 	fWatcher = NULL;
27 }
28 
29 
~BMidiRosterLooper()30 BMidiRosterLooper::~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 
60 bool
Init(BMidiRoster * roster_)61 BMidiRosterLooper::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 
91 BMidiEndpoint*
NextEndpoint(int32 * id)92 BMidiRosterLooper::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 
110 BMidiEndpoint*
FindEndpoint(int32 id)111 BMidiRosterLooper::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 
124 void
AddEndpoint(BMidiEndpoint * endp)125 BMidiRosterLooper::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 
151 void
RemoveEndpoint(BMidiEndpoint * endp)152 BMidiRosterLooper::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 
171 void
StartWatching(const BMessenger * watcher_)172 BMidiRosterLooper::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 
184 void
StopWatching()185 BMidiRosterLooper::StopWatching()
186 {
187 	delete fWatcher;
188 	fWatcher = NULL;
189 }
190 
191 
192 void
MessageReceived(BMessage * msg)193 BMidiRosterLooper::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 
212 void
OnAppRegistered(BMessage * msg)213 BMidiRosterLooper::OnAppRegistered(BMessage* msg)
214 {
215 	release_sem(fInitLock);
216 }
217 
218 
219 void
OnEndpointCreated(BMessage * msg)220 BMidiRosterLooper::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 
264 void
OnEndpointDeleted(BMessage * msg)265 BMidiRosterLooper::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 
307 void
OnEndpointChanged(BMessage * msg)308 BMidiRosterLooper::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 
331 void
OnConnectedDisconnected(BMessage * msg)332 BMidiRosterLooper::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 
370 void
ChangeRegistered(BMessage * msg,BMidiEndpoint * endp)371 BMidiRosterLooper::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 
395 void
ChangeName(BMessage * msg,BMidiEndpoint * endp)396 BMidiRosterLooper::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 
417 void
ChangeProperties(BMessage * msg,BMidiEndpoint * endp)418 BMidiRosterLooper::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 
437 void
ChangeLatency(BMessage * msg,BMidiEndpoint * endp)438 BMidiRosterLooper::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 
462 void
AllEndpoints()463 BMidiRosterLooper::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 
477 void
AllConnections()478 BMidiRosterLooper::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 
497 void
ChangeEvent(BMessage * msg,BMidiEndpoint * endp)498 BMidiRosterLooper::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 
517 void
ConnectionEvent(BMidiProducer * prod,BMidiConsumer * cons,bool mustConnect)518 BMidiRosterLooper::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 
540 void
DisconnectDeadConsumer(BMidiConsumer * cons)541 BMidiRosterLooper::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 
563 void
DisconnectDeadProducer(BMidiProducer * prod)564 BMidiRosterLooper::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 
580 int32
CountEndpoints()581 BMidiRosterLooper::CountEndpoints()
582 {
583 	return fEndpoints.CountItems();
584 }
585 
586 
587 BMidiEndpoint*
EndpointAt(int32 index)588 BMidiRosterLooper::EndpointAt(int32 index)
589 {
590 	ASSERT(index >= 0 && index < CountEndpoints())
591 
592 	return (BMidiEndpoint*) fEndpoints.ItemAt(index);
593 }
594 
595 
596 #ifdef DEBUG
597 void
DumpEndpoints()598 BMidiRosterLooper::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