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