xref: /haiku/src/servers/midi/MidiServerApp.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
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 "MidiServerApp.h"
25 #include "PortDrivers.h"
26 #include "ServerDefs.h"
27 #include "protocol.h"
28 
29 #include <Alert.h>
30 
31 #include <new>
32 
33 using std::nothrow;
34 
35 MidiServerApp::MidiServerApp()
36 	: BApplication(MIDI_SERVER_SIGNATURE)
37 {
38 	TRACE(("Running Haiku MIDI server"))
39 
40 	nextId = 1;
41 	fDeviceWatcher = new(std::nothrow) DeviceWatcher();
42 	if (fDeviceWatcher != NULL)
43 		fDeviceWatcher->Run();
44 }
45 
46 
47 MidiServerApp::~MidiServerApp()
48 {
49 	if (fDeviceWatcher && fDeviceWatcher->Lock())
50 		fDeviceWatcher->Quit();
51 
52 	for (int32 t = 0; t < CountApps(); ++t) {
53 		delete AppAt(t);
54 	}
55 
56 	for (int32 t = 0; t < CountEndpoints(); ++t) {
57 		delete EndpointAt(t);
58 	}
59 }
60 
61 
62 void
63 MidiServerApp::AboutRequested()
64 {
65 	BAlert* alert = new BAlert(0,
66 		"Haiku midi_server 1.0.0 alpha\n\n"
67 		"notes disguised as bytes\n"
68 		"propagating to endpoints,\n"
69 		"an aural delight",
70 		"OK", 0, 0, B_WIDTH_AS_USUAL,
71 		B_INFO_ALERT);
72 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
73 	alert->Go();
74 }
75 
76 
77 void
78 MidiServerApp::MessageReceived(BMessage* msg)
79 {
80 	#ifdef DEBUG
81 	printf("IN "); msg->PrintToStream();
82 	#endif
83 
84 	switch (msg->what) {
85 		case MSG_REGISTER_APP:         OnRegisterApp(msg);       break;
86 		case MSG_CREATE_ENDPOINT:      OnCreateEndpoint(msg);    break;
87 		case MSG_DELETE_ENDPOINT:      OnDeleteEndpoint(msg);    break;
88 		case MSG_PURGE_ENDPOINT:       OnPurgeEndpoint(msg);     break;
89 		case MSG_CHANGE_ENDPOINT:      OnChangeEndpoint(msg);    break;
90 		case MSG_CONNECT_ENDPOINTS:    OnConnectDisconnect(msg); break;
91 		case MSG_DISCONNECT_ENDPOINTS: OnConnectDisconnect(msg); break;
92 
93 		default: super::MessageReceived(msg); break;
94 	}
95 }
96 
97 
98 void
99 MidiServerApp::OnRegisterApp(BMessage* msg)
100 {
101 	TRACE(("MidiServerApp::OnRegisterApp"))
102 
103 	// We only send the "app registered" message upon success,
104 	// so if anything goes wrong here, we do not let the app
105 	// know about it, and we consider it unregistered. (Most
106 	// likely, the app is dead. If not, it freezes forever
107 	// in anticipation of a message that will never arrive.)
108 
109 	app_t* app = new app_t;
110 
111 	if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK) {
112 		if (SendAllEndpoints(app)) {
113 			if (SendAllConnections(app)) {
114 				BMessage reply;
115 				reply.what = MSG_APP_REGISTERED;
116 
117 				if (SendNotification(app, &reply)) {
118 					apps.AddItem(app);
119 
120 					#ifdef DEBUG
121 					DumpApps();
122 					#endif
123 
124 					return;
125 				}
126 			}
127 		}
128 	}
129 
130 	delete app;
131 }
132 
133 
134 void
135 MidiServerApp::OnCreateEndpoint(BMessage* msg)
136 {
137 	TRACE(("MidiServerApp::OnCreateEndpoint"))
138 
139 	status_t err;
140 	endpoint_t* endp = new endpoint_t;
141 
142 	endp->app = WhichApp(msg);
143 	if (endp->app == NULL) {
144 		err = B_ERROR;
145 	} else {
146 		err = B_BAD_VALUE;
147 
148 		if (msg->FindBool("midi:consumer", &endp->consumer) == B_OK
149 			&& msg->FindBool("midi:registered", &endp->registered) == B_OK
150 			&& msg->FindString("midi:name", &endp->name) == B_OK
151 			&& msg->FindMessage("midi:properties", &endp->properties) == B_OK) {
152 			if (endp->consumer) {
153 				if (msg->FindInt32("midi:port", &endp->port) == B_OK
154 					&& msg->FindInt64("midi:latency", &endp->latency) == B_OK)
155 					err = B_OK;
156 			} else
157 				err = B_OK;
158 		}
159 	}
160 
161 	BMessage reply;
162 
163 	if (err == B_OK) {
164 		endp->id = nextId++;
165 		reply.AddInt32("midi:id", endp->id);
166 	}
167 
168 	reply.AddInt32("midi:result", err);
169 
170 	if (SendReply(endp->app, msg, &reply) && err == B_OK)
171 		AddEndpoint(msg, endp);
172 	else
173 		delete endp;
174 }
175 
176 
177 void
178 MidiServerApp::OnDeleteEndpoint(BMessage* msg)
179 {
180 	TRACE(("MidiServerApp::OnDeleteEndpoint"))
181 
182 	// Clients send the "delete endpoint" message from
183 	// the BMidiEndpoint destructor, so there is no point
184 	// sending a reply, because the endpoint object will
185 	// be destroyed no matter what.
186 
187 	app_t* app = WhichApp(msg);
188 	if (app != NULL) {
189 		endpoint_t* endp = WhichEndpoint(msg, app);
190 		if (endp != NULL)
191 			RemoveEndpoint(app, endp);
192 	}
193 }
194 
195 
196 void
197 MidiServerApp::OnPurgeEndpoint(BMessage* msg)
198 {
199 	TRACE(("MidiServerApp::OnPurgeEndpoint"))
200 
201 	// This performs the same task as OnDeleteEndpoint(),
202 	// except that this message was send by the midi_server
203 	// itself, so we don't check that the app that made the
204 	// request really is the owner of the endpoint. (But we
205 	// _do_ check that the message came from the server.)
206 
207 	if (!msg->IsSourceRemote()) {
208 		int32 id;
209 		if (msg->FindInt32("midi:id", &id) == B_OK) {
210 			endpoint_t* endp = FindEndpoint(id);
211 			if (endp != NULL)
212 				RemoveEndpoint(NULL, endp);
213 		}
214 	}
215 }
216 
217 
218 void
219 MidiServerApp::OnChangeEndpoint(BMessage* msg)
220 {
221 	TRACE(("MidiServerApp::OnChangeEndpoint"))
222 
223 	endpoint_t* endp = NULL;
224 	status_t err;
225 
226 	app_t* app = WhichApp(msg);
227 	if (app == NULL)
228 		err = B_ERROR;
229 	else {
230 		endp = WhichEndpoint(msg, app);
231 		if (endp == NULL)
232 			err = B_BAD_VALUE;
233 		else
234 			err = B_OK;
235 	}
236 
237 	BMessage reply;
238 	reply.AddInt32("midi:result", err);
239 
240 	if (SendReply(app, msg, &reply) && err == B_OK) {
241 		TRACE(("Endpoint %" B_PRId32 " (%p) changed", endp->id, endp))
242 
243 		BMessage notify;
244 		notify.what = MSG_ENDPOINT_CHANGED;
245 		notify.AddInt32("midi:id", endp->id);
246 
247 		bool registered;
248 		if (msg->FindBool("midi:registered", &registered) == B_OK) {
249 			notify.AddBool("midi:registered", registered);
250 			endp->registered = registered;
251 		}
252 
253 		BString name;
254 		if (msg->FindString("midi:name", &name) == B_OK) {
255 			notify.AddString("midi:name", name);
256 			endp->name = name;
257 		}
258 
259 		BMessage properties;
260 		if (msg->FindMessage("midi:properties", &properties) == B_OK) {
261 			notify.AddMessage("midi:properties", &properties);
262 			endp->properties = properties;
263 		}
264 
265 		bigtime_t latency;
266 		if (msg->FindInt64("midi:latency", &latency) == B_OK) {
267 			notify.AddInt64("midi:latency", latency);
268 			endp->latency = latency;
269 		}
270 
271 		NotifyAll(&notify, app);
272 
273 		#ifdef DEBUG
274 		DumpEndpoints();
275 		#endif
276 	}
277 }
278 
279 
280 void
281 MidiServerApp::OnConnectDisconnect(BMessage* msg)
282 {
283 	TRACE(("MidiServerApp::OnConnectDisconnect"))
284 
285 	bool mustConnect = (msg->what == MSG_CONNECT_ENDPOINTS);
286 
287 	status_t err;
288 	endpoint_t* prod = NULL;
289 	endpoint_t* cons = NULL;
290 
291 	app_t* app = WhichApp(msg);
292 	if (app == NULL)
293 		err = B_ERROR;
294 	else {
295 		err = B_BAD_VALUE;
296 
297 		int32 prodId, consId;
298 		if (msg->FindInt32("midi:producer", &prodId) == B_OK
299 			&& msg->FindInt32("midi:consumer", &consId) == B_OK) {
300 			prod = FindEndpoint(prodId);
301 			cons = FindEndpoint(consId);
302 
303 			if (prod != NULL && !prod->consumer) {
304 				if (cons != NULL && cons->consumer) {
305 					// It is an error to connect two endpoints that
306 					// are already connected, or to disconnect two
307 					// endpoints that are not connected at all.
308 
309 					if (mustConnect == prod->connections.HasItem(cons))
310 						err = B_ERROR;
311 					else
312 						err = B_OK;
313 				}
314 			}
315 		}
316 	}
317 
318 	BMessage reply;
319 	reply.AddInt32("midi:result", err);
320 
321 	if (SendReply(app, msg, &reply) && err == B_OK) {
322 		if (mustConnect) {
323 			TRACE(("Connection made: %" B_PRId32 " ---> %" B_PRId32, prod->id,
324 				cons->id))
325 
326 			prod->connections.AddItem(cons);
327 		} else {
328 			TRACE(("Connection broken: %" B_PRId32 " -X-> %" B_PRId32, prod->id,
329 				cons->id))
330 
331 			prod->connections.RemoveItem(cons);
332 		}
333 
334 		BMessage notify;
335 		MakeConnectedNotification(&notify, prod, cons, mustConnect);
336 		NotifyAll(&notify, app);
337 
338 		#ifdef DEBUG
339 		DumpEndpoints();
340 		#endif
341 	}
342 }
343 
344 
345 bool
346 MidiServerApp::SendAllEndpoints(app_t* app)
347 {
348 	ASSERT(app != NULL)
349 
350 	BMessage notify;
351 
352 	for (int32 t = 0; t < CountEndpoints(); ++t) {
353 		endpoint_t* endp = EndpointAt(t);
354 
355 		MakeCreatedNotification(&notify, endp);
356 
357 		if (!SendNotification(app, &notify))
358 			return false;
359 	}
360 
361 	return true;
362 }
363 
364 
365 bool
366 MidiServerApp::SendAllConnections(app_t* app)
367 {
368 	ASSERT(app != NULL)
369 
370 	BMessage notify;
371 
372 	for (int32 t = 0; t < CountEndpoints(); ++t) {
373 		endpoint_t* prod = EndpointAt(t);
374 		if (!prod->consumer) {
375 			for (int32 k = 0; k < CountConnections(prod); ++k) {
376 				endpoint_t* cons = ConnectionAt(prod, k);
377 
378 				MakeConnectedNotification(&notify, prod, cons, true);
379 
380 				if (!SendNotification(app, &notify))
381 					return false;
382 			}
383 		}
384 	}
385 
386 	return true;
387 }
388 
389 
390 void
391 MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp)
392 {
393 	ASSERT(msg != NULL)
394 	ASSERT(endp != NULL)
395 	ASSERT(!endpoints.HasItem(endp))
396 
397 	TRACE(("Endpoint %" B_PRId32 " (%p) added", endp->id, endp))
398 
399 	endpoints.AddItem(endp);
400 
401 	BMessage notify;
402 	MakeCreatedNotification(&notify, endp);
403 	NotifyAll(&notify, endp->app);
404 
405 	#ifdef DEBUG
406 	DumpEndpoints();
407 	#endif
408 }
409 
410 
411 void
412 MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp)
413 {
414 	ASSERT(endp != NULL)
415 	ASSERT(endpoints.HasItem(endp))
416 
417 	TRACE(("Endpoint %" B_PRId32 " (%p) removed", endp->id, endp))
418 
419 	endpoints.RemoveItem(endp);
420 
421 	if (endp->consumer)
422 		DisconnectDeadConsumer(endp);
423 
424 	BMessage notify;
425 	notify.what = MSG_ENDPOINT_DELETED;
426 	notify.AddInt32("midi:id", endp->id);
427 	NotifyAll(&notify, app);
428 
429 	delete endp;
430 
431 	#ifdef DEBUG
432 	DumpEndpoints();
433 	#endif
434 }
435 
436 
437 void
438 MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons)
439 {
440 	ASSERT(cons != NULL)
441 	ASSERT(cons->consumer)
442 
443 	for (int32 t = 0; t < CountEndpoints(); ++t) {
444 		endpoint_t* prod = EndpointAt(t);
445 		if (!prod->consumer)
446 			prod->connections.RemoveItem(cons);
447 	}
448 }
449 
450 
451 void
452 MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp)
453 {
454 	ASSERT(msg != NULL)
455 	ASSERT(endp != NULL)
456 
457 	msg->MakeEmpty();
458 	msg->what = MSG_ENDPOINT_CREATED;
459 	msg->AddInt32("midi:id", endp->id);
460 	msg->AddBool("midi:consumer", endp->consumer);
461 	msg->AddBool("midi:registered", endp->registered);
462 	msg->AddString("midi:name", endp->name);
463 	msg->AddMessage("midi:properties", &endp->properties);
464 
465 	if (endp->consumer) {
466 		msg->AddInt32("midi:port", endp->port);
467 		msg->AddInt64("midi:latency", endp->latency);
468 	}
469 }
470 
471 
472 void
473 MidiServerApp::MakeConnectedNotification(BMessage* msg, endpoint_t* prod,
474 	endpoint_t* cons, bool mustConnect)
475 {
476 	ASSERT(msg != NULL)
477 	ASSERT(prod != NULL)
478 	ASSERT(cons != NULL)
479 	ASSERT(!prod->consumer)
480 	ASSERT(cons->consumer)
481 
482 	msg->MakeEmpty();
483 
484 	if (mustConnect)
485 		msg->what = MSG_ENDPOINTS_CONNECTED;
486 	else
487 		msg->what = MSG_ENDPOINTS_DISCONNECTED;
488 
489 	msg->AddInt32("midi:producer", prod->id);
490 	msg->AddInt32("midi:consumer", cons->id);
491 }
492 
493 
494 app_t*
495 MidiServerApp::WhichApp(BMessage* msg)
496 {
497 	ASSERT(msg != NULL)
498 
499 	BMessenger retadr = msg->ReturnAddress();
500 
501 	for (int32 t = 0; t < CountApps(); ++t) {
502 		app_t* app = AppAt(t);
503 		if (app->messenger.Team() == retadr.Team())
504 			return app;
505 	}
506 
507 	TRACE(("Application %" B_PRId32 " is not registered", retadr.Team()))
508 
509 	return NULL;
510 }
511 
512 
513 endpoint_t*
514 MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app)
515 {
516 	ASSERT(msg != NULL)
517 	ASSERT(app != NULL)
518 
519 	int32 id;
520 	if (msg->FindInt32("midi:id", &id) == B_OK) {
521 		endpoint_t* endp = FindEndpoint(id);
522 		if (endp != NULL && endp->app == app)
523 			return endp;
524 	}
525 
526 	TRACE(("Endpoint not found or wrong app"))
527 	return NULL;
528 }
529 
530 
531 endpoint_t*
532 MidiServerApp::FindEndpoint(int32 id)
533 {
534 	if (id > 0) {
535 		for (int32 t = 0; t < CountEndpoints(); ++t) {
536 			endpoint_t* endp = EndpointAt(t);
537 			if (endp->id == id)
538 				return endp;
539 		}
540 	}
541 
542 	TRACE(("Endpoint %" B_PRId32 " not found", id))
543 	return NULL;
544 }
545 
546 
547 void
548 MidiServerApp::NotifyAll(BMessage* msg, app_t* except)
549 {
550 	ASSERT(msg != NULL)
551 
552 	for (int32 t = CountApps() - 1; t >= 0; --t) {
553 		app_t* app = AppAt(t);
554 		if (app != except) {
555 			if (!SendNotification(app, msg)) {
556 				delete (app_t*)apps.RemoveItem(t);
557 
558 				#ifdef DEBUG
559 				DumpApps();
560 				#endif
561 			}
562 		}
563 	}
564 }
565 
566 
567 bool
568 MidiServerApp::SendNotification(app_t* app, BMessage* msg)
569 {
570 	ASSERT(app != NULL)
571 	ASSERT(msg != NULL)
572 
573 	status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT);
574 
575 	if (err != B_OK)
576 		DeliveryError(app);
577 
578 	return err == B_OK;
579 }
580 
581 
582 bool
583 MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply)
584 {
585 	ASSERT(msg != NULL)
586 	ASSERT(reply != NULL)
587 
588 	status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
589 
590 	if (err != B_OK && app != NULL) {
591 		DeliveryError(app);
592 		apps.RemoveItem(app);
593 		delete app;
594 
595 		#ifdef DEBUG
596 		DumpApps();
597 		#endif
598 	}
599 
600 	return err == B_OK;
601 }
602 
603 
604 void
605 MidiServerApp::DeliveryError(app_t* app)
606 {
607 	ASSERT(app != NULL)
608 
609 	// We cannot communicate with the app, so we assume it's
610 	// dead. We need to remove its endpoints from the roster,
611 	// but we cannot do that right away; removing endpoints
612 	// triggers a bunch of new notifications and we don't want
613 	// those to get in the way of the notifications we are
614 	// currently sending out. Instead, we consider the death
615 	// of an app as a separate event, and pretend that the
616 	// now-dead app sent us delete requests for its endpoints.
617 
618 	TRACE(("Delivery error; unregistering app (%p)", app))
619 
620 	BMessage msg;
621 
622 	for (int32 t = 0; t < CountEndpoints(); ++t) {
623 		endpoint_t* endp = EndpointAt(t);
624 		if (endp->app == app) {
625 			msg.MakeEmpty();
626 			msg.what = MSG_PURGE_ENDPOINT;
627 			msg.AddInt32("midi:id", endp->id);
628 
629 			// It is not safe to post a message to your own
630 			// looper's message queue, because you risk a
631 			// deadlock if the queue is full. The chance of
632 			// that happening is fairly small, but just in
633 			// case, we catch it with a timeout. Because this
634 			// situation is so unlikely, I decided to simply
635 			// forget about the whole "purge" message then.
636 
637 			if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
638 					TIMEOUT) != B_OK) {
639 				WARN("Could not deliver purge message")
640 			}
641 		}
642 	}
643 }
644 
645 
646 int32
647 MidiServerApp::CountApps()
648 {
649 	return apps.CountItems();
650 }
651 
652 
653 app_t*
654 MidiServerApp::AppAt(int32 index)
655 {
656 	ASSERT(index >= 0 && index < CountApps())
657 
658 	return (app_t*)apps.ItemAt(index);
659 }
660 
661 
662 int32
663 MidiServerApp::CountEndpoints()
664 {
665 	return endpoints.CountItems();
666 }
667 
668 
669 endpoint_t*
670 MidiServerApp::EndpointAt(int32 index)
671 {
672 	ASSERT(index >= 0 && index < CountEndpoints())
673 
674 	return (endpoint_t*)endpoints.ItemAt(index);
675 }
676 
677 
678 int32
679 MidiServerApp::CountConnections(endpoint_t* prod)
680 {
681 	ASSERT(prod != NULL)
682 	ASSERT(!prod->consumer)
683 
684 	return prod->connections.CountItems();
685 }
686 
687 
688 endpoint_t*
689 MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index)
690 {
691 	ASSERT(prod != NULL)
692 	ASSERT(!prod->consumer)
693 	ASSERT(index >= 0 && index < CountConnections(prod))
694 
695 	return (endpoint_t*)prod->connections.ItemAt(index);
696 }
697 
698 
699 #ifdef DEBUG
700 void
701 MidiServerApp::DumpApps()
702 {
703 	printf("*** START DumpApps\n");
704 
705 	for (int32 t = 0; t < CountApps(); ++t) {
706 		app_t* app = AppAt(t);
707 
708 		printf("\tapp %" B_PRId32 " (%p): team %" B_PRId32 "\n", t, app,
709 			app->messenger.Team());
710 	}
711 
712 	printf("*** END DumpApps\n");
713 }
714 #endif
715 
716 
717 #ifdef DEBUG
718 void
719 MidiServerApp::DumpEndpoints()
720 {
721 	printf("*** START DumpEndpoints\n");
722 
723 	for (int32 t = 0; t < CountEndpoints(); ++t) {
724 		endpoint_t* endp = EndpointAt(t);
725 
726 		printf("\tendpoint %" B_PRId32 " (%p):\n", t, endp);
727 		printf("\t\tid %" B_PRId32 ", name '%s', %s, %s, app %p\n",
728 			endp->id, endp->name.String(),
729 			endp->consumer ? "consumer" : "producer",
730 			endp->registered ? "registered" : "unregistered",
731 			endp->app);
732 		printf("\t\tproperties: "); endp->properties.PrintToStream();
733 
734 		if (endp->consumer)
735 			printf("\t\tport %" B_PRId32 ", latency %" B_PRIdBIGTIME "\n",
736 				endp->port, endp->latency);
737 		else {
738 			printf("\t\tconnections:\n");
739 			for (int32 k = 0; k < CountConnections(endp); ++k) {
740 				endpoint_t* cons = ConnectionAt(endp, k);
741 				printf("\t\t\tid %" B_PRId32 " (%p)\n", cons->id, cons);
742 			}
743 		}
744 	}
745 
746 	printf("*** END DumpEndpoints\n");
747 }
748 #endif
749 
750 
751 //	#pragma mark -
752 
753 
754 int
755 main()
756 {
757 	MidiServerApp app;
758 	app.Run();
759 	return 0;
760 }
761 
762