xref: /haiku/src/servers/midi/MidiServerApp.cpp (revision 6c4a44e36ba846c54467103f884d65dfa13e7fcb)
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 %ld (%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: %ld ---> %ld", prod->id, cons->id))
324 
325 			prod->connections.AddItem(cons);
326 		} else {
327 			TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id))
328 
329 			prod->connections.RemoveItem(cons);
330 		}
331 
332 		BMessage notify;
333 		MakeConnectedNotification(&notify, prod, cons, mustConnect);
334 		NotifyAll(&notify, app);
335 
336 		#ifdef DEBUG
337 		DumpEndpoints();
338 		#endif
339 	}
340 }
341 
342 
343 bool
344 MidiServerApp::SendAllEndpoints(app_t* app)
345 {
346 	ASSERT(app != NULL)
347 
348 	BMessage notify;
349 
350 	for (int32 t = 0; t < CountEndpoints(); ++t) {
351 		endpoint_t* endp = EndpointAt(t);
352 
353 		MakeCreatedNotification(&notify, endp);
354 
355 		if (!SendNotification(app, &notify))
356 			return false;
357 	}
358 
359 	return true;
360 }
361 
362 
363 bool
364 MidiServerApp::SendAllConnections(app_t* app)
365 {
366 	ASSERT(app != NULL)
367 
368 	BMessage notify;
369 
370 	for (int32 t = 0; t < CountEndpoints(); ++t) {
371 		endpoint_t* prod = EndpointAt(t);
372 		if (!prod->consumer) {
373 			for (int32 k = 0; k < CountConnections(prod); ++k) {
374 				endpoint_t* cons = ConnectionAt(prod, k);
375 
376 				MakeConnectedNotification(&notify, prod, cons, true);
377 
378 				if (!SendNotification(app, &notify))
379 					return false;
380 			}
381 		}
382 	}
383 
384 	return true;
385 }
386 
387 
388 void
389 MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp)
390 {
391 	ASSERT(msg != NULL)
392 	ASSERT(endp != NULL)
393 	ASSERT(!endpoints.HasItem(endp))
394 
395 	TRACE(("Endpoint %ld (%p) added", endp->id, endp))
396 
397 	endpoints.AddItem(endp);
398 
399 	BMessage notify;
400 	MakeCreatedNotification(&notify, endp);
401 	NotifyAll(&notify, endp->app);
402 
403 	#ifdef DEBUG
404 	DumpEndpoints();
405 	#endif
406 }
407 
408 
409 void
410 MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp)
411 {
412 	ASSERT(endp != NULL)
413 	ASSERT(endpoints.HasItem(endp))
414 
415 	TRACE(("Endpoint %ld (%p) removed", endp->id, endp))
416 
417 	endpoints.RemoveItem(endp);
418 
419 	if (endp->consumer)
420 		DisconnectDeadConsumer(endp);
421 
422 	BMessage notify;
423 	notify.what = MSG_ENDPOINT_DELETED;
424 	notify.AddInt32("midi:id", endp->id);
425 	NotifyAll(&notify, app);
426 
427 	delete endp;
428 
429 	#ifdef DEBUG
430 	DumpEndpoints();
431 	#endif
432 }
433 
434 
435 void
436 MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons)
437 {
438 	ASSERT(cons != NULL)
439 	ASSERT(cons->consumer)
440 
441 	for (int32 t = 0; t < CountEndpoints(); ++t) {
442 		endpoint_t* prod = EndpointAt(t);
443 		if (!prod->consumer)
444 			prod->connections.RemoveItem(cons);
445 	}
446 }
447 
448 
449 void
450 MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp)
451 {
452 	ASSERT(msg != NULL)
453 	ASSERT(endp != NULL)
454 
455 	msg->MakeEmpty();
456 	msg->what = MSG_ENDPOINT_CREATED;
457 	msg->AddInt32("midi:id", endp->id);
458 	msg->AddBool("midi:consumer", endp->consumer);
459 	msg->AddBool("midi:registered", endp->registered);
460 	msg->AddString("midi:name", endp->name);
461 	msg->AddMessage("midi:properties", &endp->properties);
462 
463 	if (endp->consumer) {
464 		msg->AddInt32("midi:port", endp->port);
465 		msg->AddInt64("midi:latency", endp->latency);
466 	}
467 }
468 
469 
470 void
471 MidiServerApp::MakeConnectedNotification(BMessage* msg, endpoint_t* prod,
472 	endpoint_t* cons, bool mustConnect)
473 {
474 	ASSERT(msg != NULL)
475 	ASSERT(prod != NULL)
476 	ASSERT(cons != NULL)
477 	ASSERT(!prod->consumer)
478 	ASSERT(cons->consumer)
479 
480 	msg->MakeEmpty();
481 
482 	if (mustConnect)
483 		msg->what = MSG_ENDPOINTS_CONNECTED;
484 	else
485 		msg->what = MSG_ENDPOINTS_DISCONNECTED;
486 
487 	msg->AddInt32("midi:producer", prod->id);
488 	msg->AddInt32("midi:consumer", cons->id);
489 }
490 
491 
492 app_t*
493 MidiServerApp::WhichApp(BMessage* msg)
494 {
495 	ASSERT(msg != NULL)
496 
497 	BMessenger retadr = msg->ReturnAddress();
498 
499 	for (int32 t = 0; t < CountApps(); ++t) {
500 		app_t* app = AppAt(t);
501 		if (app->messenger.Team() == retadr.Team())
502 			return app;
503 	}
504 
505 	TRACE(("Application %ld is not registered", retadr.Team()))
506 
507 	return NULL;
508 }
509 
510 
511 endpoint_t*
512 MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app)
513 {
514 	ASSERT(msg != NULL)
515 	ASSERT(app != NULL)
516 
517 	int32 id;
518 	if (msg->FindInt32("midi:id", &id) == B_OK) {
519 		endpoint_t* endp = FindEndpoint(id);
520 		if (endp != NULL && endp->app == app)
521 			return endp;
522 	}
523 
524 	TRACE(("Endpoint not found or wrong app"))
525 	return NULL;
526 }
527 
528 
529 endpoint_t*
530 MidiServerApp::FindEndpoint(int32 id)
531 {
532 	if (id > 0) {
533 		for (int32 t = 0; t < CountEndpoints(); ++t) {
534 			endpoint_t* endp = EndpointAt(t);
535 			if (endp->id == id)
536 				return endp;
537 		}
538 	}
539 
540 	TRACE(("Endpoint %ld not found", id))
541 	return NULL;
542 }
543 
544 
545 void
546 MidiServerApp::NotifyAll(BMessage* msg, app_t* except)
547 {
548 	ASSERT(msg != NULL)
549 
550 	for (int32 t = CountApps() - 1; t >= 0; --t) {
551 		app_t* app = AppAt(t);
552 		if (app != except) {
553 			if (!SendNotification(app, msg)) {
554 				delete (app_t*)apps.RemoveItem(t);
555 
556 				#ifdef DEBUG
557 				DumpApps();
558 				#endif
559 			}
560 		}
561 	}
562 }
563 
564 
565 bool
566 MidiServerApp::SendNotification(app_t* app, BMessage* msg)
567 {
568 	ASSERT(app != NULL)
569 	ASSERT(msg != NULL)
570 
571 	status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT);
572 
573 	if (err != B_OK)
574 		DeliveryError(app);
575 
576 	return err == B_OK;
577 }
578 
579 
580 bool
581 MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply)
582 {
583 	ASSERT(msg != NULL)
584 	ASSERT(reply != NULL)
585 
586 	status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
587 
588 	if (err != B_OK && app != NULL) {
589 		DeliveryError(app);
590 		apps.RemoveItem(app);
591 		delete app;
592 
593 		#ifdef DEBUG
594 		DumpApps();
595 		#endif
596 	}
597 
598 	return err == B_OK;
599 }
600 
601 
602 void
603 MidiServerApp::DeliveryError(app_t* app)
604 {
605 	ASSERT(app != NULL)
606 
607 	// We cannot communicate with the app, so we assume it's
608 	// dead. We need to remove its endpoints from the roster,
609 	// but we cannot do that right away; removing endpoints
610 	// triggers a bunch of new notifications and we don't want
611 	// those to get in the way of the notifications we are
612 	// currently sending out. Instead, we consider the death
613 	// of an app as a separate event, and pretend that the
614 	// now-dead app sent us delete requests for its endpoints.
615 
616 	TRACE(("Delivery error; unregistering app (%p)", app))
617 
618 	BMessage msg;
619 
620 	for (int32 t = 0; t < CountEndpoints(); ++t) {
621 		endpoint_t* endp = EndpointAt(t);
622 		if (endp->app == app) {
623 			msg.MakeEmpty();
624 			msg.what = MSG_PURGE_ENDPOINT;
625 			msg.AddInt32("midi:id", endp->id);
626 
627 			// It is not safe to post a message to your own
628 			// looper's message queue, because you risk a
629 			// deadlock if the queue is full. The chance of
630 			// that happening is fairly small, but just in
631 			// case, we catch it with a timeout. Because this
632 			// situation is so unlikely, I decided to simply
633 			// forget about the whole "purge" message then.
634 
635 			if (be_app_messenger.SendMessage(&msg, (BHandler*)NULL,
636 					TIMEOUT) != B_OK) {
637 				WARN("Could not deliver purge message")
638 			}
639 		}
640 	}
641 }
642 
643 
644 int32
645 MidiServerApp::CountApps()
646 {
647 	return apps.CountItems();
648 }
649 
650 
651 app_t*
652 MidiServerApp::AppAt(int32 index)
653 {
654 	ASSERT(index >= 0 && index < CountApps())
655 
656 	return (app_t*)apps.ItemAt(index);
657 }
658 
659 
660 int32
661 MidiServerApp::CountEndpoints()
662 {
663 	return endpoints.CountItems();
664 }
665 
666 
667 endpoint_t*
668 MidiServerApp::EndpointAt(int32 index)
669 {
670 	ASSERT(index >= 0 && index < CountEndpoints())
671 
672 	return (endpoint_t*)endpoints.ItemAt(index);
673 }
674 
675 
676 int32
677 MidiServerApp::CountConnections(endpoint_t* prod)
678 {
679 	ASSERT(prod != NULL)
680 	ASSERT(!prod->consumer)
681 
682 	return prod->connections.CountItems();
683 }
684 
685 
686 endpoint_t*
687 MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index)
688 {
689 	ASSERT(prod != NULL)
690 	ASSERT(!prod->consumer)
691 	ASSERT(index >= 0 && index < CountConnections(prod))
692 
693 	return (endpoint_t*)prod->connections.ItemAt(index);
694 }
695 
696 
697 #ifdef DEBUG
698 void
699 MidiServerApp::DumpApps()
700 {
701 	printf("*** START DumpApps\n");
702 
703 	for (int32 t = 0; t < CountApps(); ++t) {
704 		app_t* app = AppAt(t);
705 
706 		printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team());
707 	}
708 
709 	printf("*** END DumpApps\n");
710 }
711 #endif
712 
713 
714 #ifdef DEBUG
715 void
716 MidiServerApp::DumpEndpoints()
717 {
718 	printf("*** START DumpEndpoints\n");
719 
720 	for (int32 t = 0; t < CountEndpoints(); ++t) {
721 		endpoint_t* endp = EndpointAt(t);
722 
723 		printf("\tendpoint %ld (%p):\n", t, endp);
724 		printf("\t\tid %ld, name '%s', %s, %s, app %p\n",
725 			endp->id, endp->name.String(),
726 			endp->consumer ? "consumer" : "producer",
727 			endp->registered ? "registered" : "unregistered",
728 			endp->app);
729 		printf("\t\tproperties: "); endp->properties.PrintToStream();
730 
731 		if (endp->consumer)
732 			printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency);
733 		else {
734 			printf("\t\tconnections:\n");
735 			for (int32 k = 0; k < CountConnections(endp); ++k) {
736 				endpoint_t* cons = ConnectionAt(endp, k);
737 				printf("\t\t\tid %ld (%p)\n", cons->id, cons);
738 			}
739 		}
740 	}
741 
742 	printf("*** END DumpEndpoints\n");
743 }
744 #endif
745 
746 
747 //	#pragma mark -
748 
749 
750 int
751 main()
752 {
753 	MidiServerApp app;
754 	app.Run();
755 	return 0;
756 }
757 
758