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