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(¬ify, 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(¬ify, 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(¬ify, 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(¬ify, 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(¬ify, 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(¬ify, 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(¬ify);
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