1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
3 * LADI Session Handler (ladish)
5 * Copyright (C) 2009,2010,2011,2012 Nedko Arnaudov <nedko@arnaudov.name>
7 **************************************************************************
8 * This file contains implementation of the graph virtualizer object
9 **************************************************************************
11 * LADI Session Handler is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * LADI Session Handler is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
23 * or write to the Free Software Foundation, Inc.,
24 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include "virtualizer.h"
28 #include "../dbus_constants.h"
29 #include "../proxies/a2j_proxy.h"
30 #include "../proxies/jmcore_proxy.h"
32 #include "app_supervisor.h"
33 #include "studio_internal.h"
34 #include "../common/catdup.h"
37 #include "../alsapid/alsapid.h"
41 graph_proxy_handle jack_graph_proxy
;
42 ladish_graph_handle jack_graph
;
43 uint64_t system_client_id
;
44 uint64_t system_midi_client_id
;
45 unsigned int our_clients_count
;
48 /* 47c1cd18-7b21-4389-bec4-6e0658e1d6b1 */
49 UUID_DEFINE(g_system_capture_uuid
,0x47,0xC1,0xCD,0x18,0x7B,0x21,0x43,0x89,0xBE,0xC4,0x6E,0x06,0x58,0xE1,0xD6,0xB1);
51 /* b2a0bb06-28d8-4bfe-956e-eb24378f9629 */
52 UUID_DEFINE(g_system_playback_uuid
,0xB2,0xA0,0xBB,0x06,0x28,0xD8,0x4B,0xFE,0x95,0x6E,0xEB,0x24,0x37,0x8F,0x96,0x29);
54 /* be23a242-e2b2-11de-b795-002618af5e42 */
55 UUID_DEFINE(g_a2j_uuid
,0xBE,0x23,0xA2,0x42,0xE2,0xB2,0x11,0xDE,0xB7,0x95,0x00,0x26,0x18,0xAF,0x5E,0x42);
57 struct app_find_context
60 ladish_graph_handle graph
;
61 ladish_app_handle app
;
64 #define app_find_context_ptr ((struct app_find_context *)context)
66 static bool lookup_app_in_supervisor(void * context
, ladish_graph_handle graph
, ladish_app_supervisor_handle app_supervisor
)
69 ladish_app_handle app
;
71 /* we stop iteration when app is found */
72 ASSERT(app_find_context_ptr
->app
== NULL
&& app_find_context_ptr
->graph
== NULL
);
74 //log_info("checking app supervisor \"%s\" for pid %llu", ladish_app_supervisor_get_name(app_supervisor), (unsigned long long)pid);
76 pid
= app_find_context_ptr
->pid
;
79 app
= ladish_app_supervisor_find_app_by_pid(app_supervisor
, pid
);
83 pid
= (pid_t
)procfs_get_process_parent((unsigned long long)pid
);
87 log_info("parent pid %llu", (unsigned long long)pid
);
94 { /* app not found in current supervisor */
95 return true; /* continue app supervisor iteration */
98 app_find_context_ptr
->app
= app
;
99 app_find_context_ptr
->graph
= graph
;
101 return false; /* stop app supervisor iteration */
104 #undef app_find_context_ptr
106 ladish_app_handle
ladish_find_app_by_pid(pid_t pid
, ladish_graph_handle
* graph_ptr
)
108 struct app_find_context context
;
112 context
.graph
= NULL
;
114 ladish_studio_iterate_virtual_graphs(&context
, lookup_app_in_supervisor
);
116 if (context
.app
== NULL
)
117 { /* app not found */
118 ASSERT(context
.graph
== NULL
);
122 ASSERT(context
.graph
!= NULL
);
123 if (graph_ptr
!= NULL
)
125 *graph_ptr
= context
.graph
;
131 struct find_link_port_context
135 ladish_port_handle port
;
136 ladish_graph_handle graph
;
139 #define find_link_port_context_ptr ((struct find_link_port_context *)context)
143 find_link_port_vgraph_callback_by_uuid(
145 ladish_graph_handle graph
,
146 ladish_app_supervisor_handle
UNUSED(app_supervisor
))
148 ladish_port_handle port
;
150 port
= ladish_graph_find_port_by_uuid(graph
, find_link_port_context_ptr
->uuid
, true, NULL
);
153 find_link_port_context_ptr
->port
= port
;
154 find_link_port_context_ptr
->graph
= graph
;
158 return true; /* continue vgraph iteration */
163 find_link_port_vgraph_callback_by_jack_id(
165 ladish_graph_handle graph
,
166 ladish_app_supervisor_handle
UNUSED(app_supervisor
))
168 ladish_port_handle port
;
171 log_info("searching link port with jack id %"PRIu64
" in graph %s", find_link_port_context_ptr
->jack_id
, ladish_graph_get_description(graph
));
173 room
= graph
!= g_studio
.studio_graph
;
175 port
= ladish_graph_find_port_by_jack_id(graph
, find_link_port_context_ptr
->jack_id
, room
, !room
);
178 find_link_port_context_ptr
->port
= port
;
179 find_link_port_context_ptr
->graph
= graph
;
183 return true; /* continue vgraph iteration */
186 #undef find_link_port_context_ptr
190 find_link_port_vgraph_by_uuid(
191 struct virtualizer
* UNUSED(virtualizer_ptr
),
192 const char * port_name
,
193 ladish_port_handle
* port_ptr
)
195 struct find_link_port_context context
;
197 uuid_parse(port_name
, context
.uuid
);
198 context
.graph
= NULL
;
201 ladish_studio_iterate_virtual_graphs(&context
, find_link_port_vgraph_callback_by_uuid
);
203 if (port_ptr
!= NULL
&& context
.graph
!= NULL
)
205 *port_ptr
= context
.port
;
208 return context
.graph
;
213 find_link_port_vgraph_by_jack_id(
214 struct virtualizer
* UNUSED(virtualizer_ptr
),
216 ladish_port_handle
* port_ptr
)
218 struct find_link_port_context context
;
220 context
.jack_id
= jack_id
;
221 context
.graph
= NULL
;
224 ladish_studio_iterate_virtual_graphs(&context
, find_link_port_vgraph_callback_by_jack_id
);
226 if (port_ptr
!= NULL
&& context
.graph
!= NULL
)
228 *port_ptr
= context
.port
;
231 return context
.graph
;
237 struct virtualizer
* virtualizer_ptr
,
239 ladish_port_handle
* port_ptr
,
240 ladish_graph_handle
* vgraph_ptr
)
242 ladish_port_handle port
;
243 ladish_graph_handle vgraph
;
245 port
= ladish_graph_find_port_by_jack_id(virtualizer_ptr
->jack_graph
, port_id
, true, true);
248 log_error("Unknown JACK port with id %"PRIu64
" (dis)connected", port_id
);
252 vgraph
= ladish_port_get_vgraph(port
);
255 vgraph
= find_link_port_vgraph_by_jack_id(virtualizer_ptr
, port_id
, NULL
);
258 log_error("Cannot find vgraph for (dis)connected jmcore port");
262 log_info("link port found in graph %s", ladish_graph_get_description(vgraph
));
266 *vgraph_ptr
= vgraph
;
270 #define virtualizer_ptr ((struct virtualizer *)context)
272 static void clear(void * UNUSED(context
))
277 static void client_appeared(void * context
, uint64_t id
, const char * jack_name
)
279 ladish_client_handle client
;
280 const char * a2j_name
;
282 ladish_app_handle app
;
286 ladish_graph_handle graph
;
289 log_info("client_appeared(%"PRIu64
", %s)", id
, jack_name
);
291 a2j_name
= a2j_proxy_get_jack_client_name_cached();
292 is_a2j
= a2j_name
!= NULL
&& strcmp(a2j_name
, jack_name
) == 0;
299 if (!graph_proxy_get_client_pid(virtualizer_ptr
->jack_graph_proxy
, id
, &pid
))
301 log_info("client %"PRIu64
" pid is unknown", id
);
305 log_info("client pid is %"PRId64
, (int64_t)pid
);
307 if (pid
!= 0) /* skip internal clients that will match the pending clients in the graph, both have zero pid */
309 jmcore
= pid
== jmcore_proxy_get_pid_cached();
312 log_info("jmcore client appeared");
316 app
= ladish_find_app_by_pid(pid
, &graph
);
319 ladish_app_get_uuid(app
, app_uuid
);
320 ASSERT(!uuid_is_null(app_uuid
));
321 name
= ladish_app_get_name(app
);
322 log_info("app name is '%s'", name
);
332 client
= ladish_graph_find_client_by_uuid(virtualizer_ptr
->jack_graph
, g_a2j_uuid
);
338 client
= ladish_graph_find_client_by_app(virtualizer_ptr
->jack_graph
, app_uuid
);
341 log_info("Lookup by app uuid failed, attempting lookup by name '%s'", name
);
348 client
= ladish_graph_find_client_by_name(virtualizer_ptr
->jack_graph
, name
, true);
354 log_info("found existing client");
355 if (ladish_client_get_jack_id(client
) != 0)
357 log_error("Ignoring client with duplicate name '%s' ('%s')", name
, jack_name
);
361 ladish_client_set_jack_name(client
, jack_name
);
363 ladish_client_set_jack_id(client
, id
);
364 ladish_graph_show_client(virtualizer_ptr
->jack_graph
, client
);
369 if (!ladish_client_create(is_a2j
? g_a2j_uuid
: NULL
, &client
))
371 log_error("ladish_client_create() failed. Ignoring client %"PRIu64
" (%s)", id
, jack_name
);
375 ladish_client_set_jack_id(client
, id
);
376 ladish_client_set_jack_name(client
, jack_name
);
378 if (!ladish_graph_add_client(virtualizer_ptr
->jack_graph
, client
, name
, false))
380 log_error("ladish_graph_add_client() failed to add client %"PRIu64
" (%s) to JACK graph", id
, name
);
381 ladish_client_destroy(client
);
386 if (strcmp(jack_name
, "system") == 0)
388 virtualizer_ptr
->system_client_id
= id
;
391 if (strcmp(jack_name
, "system_midi") == 0)
393 virtualizer_ptr
->system_midi_client_id
= id
;
398 /* interlink client and app */
399 ladish_app_add_pid(app
, pid
);
400 ladish_client_set_pid(client
, pid
);
401 ladish_client_set_app(client
, app_uuid
);
404 ladish_client_set_vgraph(client
, graph
);
405 virtualizer_ptr
->our_clients_count
++;
409 ladish_client_set_pid(client
, pid
);
410 ASSERT(ladish_client_get_vgraph(client
) == NULL
);
414 /* unknown and internal clients appear in the studio graph */
415 ladish_client_set_vgraph(client
, g_studio
.studio_graph
);
422 static void port_disappeared(void * context
, uint64_t client_id
, uint64_t port_id
);
425 force_port_disappear(
427 ladish_graph_handle
UNUSED(graph_handle
),
429 ladish_client_handle client_handle
,
430 const char * client_name
,
431 ladish_port_handle port_handle
,
432 const char * port_name
,
433 uint32_t UNUSED(port_type
),
434 uint32_t UNUSED(port_flags
))
444 log_error("forcing disappear of port '%s':'%s'", client_name
, port_name
);
446 client_id
= ladish_client_get_jack_id(client_handle
);
447 port_id
= ladish_port_get_jack_id(port_handle
);
448 port_disappeared(context
, client_id
, port_id
);
453 static void client_disappeared(void * context
, uint64_t id
)
455 ladish_client_handle client
;
458 ladish_app_handle app
;
459 ladish_graph_handle vgraph
;
461 log_info("client_disappeared(%"PRIu64
")", id
);
463 client
= ladish_graph_find_client_by_jack_id(virtualizer_ptr
->jack_graph
, id
);
466 log_error("Unknown JACK client with id %"PRIu64
" disappeared", id
);
470 log_info("client disappeared: '%s'", ladish_graph_get_client_name(virtualizer_ptr
->jack_graph
, client
));
472 /* This is a workaround for jack2/jackdbus bug. */
473 ladish_graph_interate_client_ports(virtualizer_ptr
->jack_graph
, client
, context
, force_port_disappear
);
475 vgraph
= ladish_client_get_vgraph(client
);
477 pid
= ladish_client_get_pid(client
);
478 if (ladish_client_get_app(client
, app_uuid
))
480 virtualizer_ptr
->our_clients_count
--;
481 app
= ladish_studio_find_app_by_uuid(app_uuid
);
484 ladish_app_del_pid(app
, pid
);
488 log_error("app of disappearing client %"PRIu64
" not found. pid is %"PRIu64
, id
, (uint64_t)pid
);
493 if (id
== virtualizer_ptr
->system_client_id
)
495 virtualizer_ptr
->system_client_id
= 0;
498 if (id
== virtualizer_ptr
->system_midi_client_id
)
500 virtualizer_ptr
->system_midi_client_id
= 0;
503 if (vgraph
!= NULL
&& ladish_graph_is_persist(vgraph
)) /* if client is supposed to be persisted */
505 ladish_client_set_jack_id(client
, 0);
506 ladish_graph_hide_client(virtualizer_ptr
->jack_graph
, client
);
510 ladish_graph_remove_client(virtualizer_ptr
->jack_graph
, client
);
511 ladish_client_destroy(client
);
512 /* no need to clear vclient interlink because it either does not exist (vgraph is NULL) or
513 * it will be destroyed before it is accessed (persist flag is cleared on room deletion) */
523 const char * real_jack_port_name
,
528 ladish_client_handle jack_client
;
529 ladish_client_handle vclient
;
530 ladish_port_handle port
;
533 const char * jack_client_name
;
534 const char * vclient_name
;
538 ladish_app_handle app
;
541 char * alsa_client_name
;
542 char * alsa_port_name
;
543 char * a2j_fake_jack_port_name
;
544 uint32_t alsa_client_id
;
545 const char * jack_port_name
;
546 const char * vport_name
;
547 ladish_graph_handle vgraph
;
549 log_info("port_appeared(%"PRIu64
", %"PRIu64
", %s (%s, %s))", client_id
, port_id
, real_jack_port_name
, is_input
? "in" : "out", is_midi
? "midi" : "audio");
551 alsa_client_name
= NULL
;
552 alsa_port_name
= NULL
;
553 a2j_fake_jack_port_name
= NULL
;
555 type
= is_midi
? JACKDBUS_PORT_TYPE_MIDI
: JACKDBUS_PORT_TYPE_AUDIO
;
556 flags
= is_input
? JACKDBUS_PORT_FLAG_INPUT
: JACKDBUS_PORT_FLAG_OUTPUT
;
559 flags
|= JACKDBUS_PORT_FLAG_TERMINAL
;
562 /********************/
563 /* gather info about the appeared port */
565 jack_client
= ladish_graph_find_client_by_jack_id(virtualizer_ptr
->jack_graph
, client_id
);
566 if (jack_client
== NULL
)
568 log_error("Port of unknown JACK client with id %"PRIu64
" appeared", client_id
);
572 pid
= ladish_client_get_pid(jack_client
);
573 has_app
= ladish_client_get_app(jack_client
, app_uuid
);
575 /* find the virtual graph that owns the app that owns the client that owns the appeared port */
576 vgraph
= ladish_client_get_vgraph(jack_client
);
579 vgraph
= find_link_port_vgraph_by_uuid(virtualizer_ptr
, real_jack_port_name
, &port
);
582 log_error("Cannot find vgraph for appeared jmcore port '%s'", real_jack_port_name
);
586 /* jmcore port appeared */
588 log_info("jmcore port appeared in vgraph %s", ladish_graph_get_description(vgraph
));
590 if (!ladish_graph_add_port(virtualizer_ptr
->jack_graph
, jack_client
, port
, real_jack_port_name
, type
, flags
, false))
592 log_error("ladish_graph_add_port() failed.");
596 if (vgraph
== g_studio
.studio_graph
)
598 ladish_port_set_jack_id(port
, port_id
);
602 ladish_port_set_jack_id_room(port
, port_id
);
605 vclient
= ladish_graph_get_port_client(vgraph
, port
);
608 log_error("link port client not found in vgraph %s", ladish_graph_get_description(vgraph
));
613 ladish_graph_show_port(vgraph
, port
);
618 //log_info("Port of virtual graph '%s'", ladish_graph_get_description(vgraph));
621 jack_client_name
= ladish_graph_get_client_name(virtualizer_ptr
->jack_graph
, jack_client
);
623 is_a2j
= ladish_virtualizer_is_a2j_client(jack_client
);
626 log_info("a2j port appeared");
627 if (!a2j_proxy_map_jack_port(real_jack_port_name
, &alsa_client_name
, &alsa_port_name
, &alsa_client_id
))
630 alsa_client_name
= catdup("FAILED ", jack_client_name
);
631 if (alsa_client_name
== NULL
)
633 log_error("catdup failed to duplicate a2j jack client name after map failure");
637 alsa_port_name
= strdup(real_jack_port_name
);
638 if (alsa_port_name
== NULL
)
640 log_error("catdup failed to duplicate a2j jack port name after map failure");
641 free(alsa_client_name
);
645 vclient_name
= alsa_client_name
;
649 log_info("a2j: '%s':'%s' (%"PRIu32
")", alsa_client_name
, alsa_port_name
, alsa_client_id
);
650 vclient_name
= alsa_client_name
;
651 if (alsapid_get_pid(alsa_client_id
, &pid
))
653 log_info("ALSA client pid is %lld", (long long)pid
);
655 app
= ladish_find_app_by_pid(pid
, &vgraph
);
658 ladish_app_get_uuid(app
, app_uuid
);
659 ASSERT(!uuid_is_null(app_uuid
));
660 vclient_name
= ladish_app_get_name(app
);
662 log_info("ALSA app name is '%s'", vclient_name
);
663 ladish_app_add_pid(app
, pid
);
668 log_error("UNKNOWN ALSA client pid");
672 a2j_fake_jack_port_name
= catdup4(vclient_name
, is_input
? " (playback)" : " (capture)", ": ", alsa_port_name
);
673 if (a2j_fake_jack_port_name
== NULL
)
675 log_error("catdup4() failed");
676 goto free_alsa_names
;
679 jack_port_name
= a2j_fake_jack_port_name
;
683 vclient_name
= jack_client_name
;
684 jack_port_name
= real_jack_port_name
;
687 /********************/
689 /* search (by name) the appeared port in jack graph
690 * if found - show it in both graphs.
691 * if not found - create new port and add it to the jack graph.
692 * Then process to adding it to virtual graph */
694 port
= ladish_graph_find_port_by_name(virtualizer_ptr
->jack_graph
, jack_client
, jack_port_name
, vgraph
);
697 log_info("found existing port %p", port
);
699 if (ladish_port_get_jack_id(port
) != 0)
701 log_error("Ignoring duplicate JACK port '%s':'%s'", jack_client_name
, jack_port_name
);
702 goto free_alsa_names
;
705 ladish_port_set_jack_id(port
, port_id
);
706 ladish_graph_adjust_port(virtualizer_ptr
->jack_graph
, port
, type
, flags
);
707 ladish_graph_show_port(virtualizer_ptr
->jack_graph
, port
);
709 vclient
= ladish_graph_get_port_client(vgraph
, port
);
712 log_error("JACK port not found in virtual graph '%s'", ladish_graph_get_description(vgraph
));
713 ladish_graph_dump(g_studio
.jack_graph
);
714 ladish_graph_dump(vgraph
);
716 goto free_alsa_names
;
719 /* for normal ports, one can find the app_uuid through the jack client,
720 but for a2j ports the jack client is shared between graphs */
721 /* clients and ports can be reused if they were started externally then through ladish */
724 ladish_port_set_app(port
, app_uuid
);
725 ladish_port_set_pid(port
, pid
);
726 if (!ladish_client_has_app(vclient
))
728 ladish_client_set_app(vclient
, app_uuid
);
732 ladish_dict_set(ladish_port_get_dict(port
), URI_A2J_PORT
, is_a2j
? "yes" : "no");
734 ladish_client_set_jack_id(vclient
, client_id
);
735 ladish_graph_adjust_port(vgraph
, port
, type
, flags
);
736 ladish_graph_show_port(vgraph
, port
);
737 goto free_alsa_names
;
740 if (!ladish_port_create(NULL
, false, &port
))
742 log_error("ladish_port_create() failed.");
743 goto free_alsa_names
;
746 /* set port jack id so invisible connections to/from it can be restored */
747 ladish_port_set_jack_id(port
, port_id
);
749 /* for normal ports, one can find the vgraph and app_uuid through the jack client,
750 but for a2j ports the jack client is shared between graphs */
751 ladish_port_set_vgraph(port
, vgraph
);
754 ladish_port_set_app(port
, app_uuid
);
755 ladish_port_set_pid(port
, pid
);
758 ladish_dict_set(ladish_port_get_dict(port
), URI_A2J_PORT
, is_a2j
? "yes" : "no");
760 if (!ladish_graph_add_port(virtualizer_ptr
->jack_graph
, jack_client
, port
, jack_port_name
, type
, flags
, false))
762 log_error("ladish_graph_add_port() failed.");
763 ladish_port_destroy(port
);
764 goto free_alsa_names
;
767 /********************/
768 /* find/create the virtual client where port will be added */
772 vclient
= ladish_graph_find_client_by_name(vgraph
, vclient_name
, false);
775 if (!ladish_client_create(NULL
, &vclient
))
777 log_error("ladish_client_create() failed.");
778 goto free_alsa_names
;
783 ladish_client_set_app(vclient
, app_uuid
);
786 if (!ladish_graph_add_client(vgraph
, vclient
, vclient_name
, false))
788 log_error("ladish_graph_add_client() failed.");
789 ladish_client_destroy(vclient
);
790 goto free_alsa_names
;
794 else if (client_id
== virtualizer_ptr
->system_client_id
||
795 client_id
== virtualizer_ptr
->system_midi_client_id
)
797 log_info("system client port appeared");
800 { /* output capture port */
802 vclient
= ladish_graph_find_client_by_uuid(vgraph
, g_system_capture_uuid
);
805 if (!ladish_client_create(g_system_capture_uuid
, &vclient
))
807 log_error("ladish_client_create() failed.");
808 goto free_alsa_names
;
811 if (!ladish_graph_add_client(vgraph
, vclient
, "Hardware Capture", false))
813 log_error("ladish_graph_add_client() failed.");
814 ladish_client_destroy(vclient
);
815 goto free_alsa_names
;
820 { /* input playback port */
821 vclient
= ladish_graph_find_client_by_uuid(vgraph
, g_system_playback_uuid
);
824 if (!ladish_client_create(g_system_playback_uuid
, &vclient
))
826 log_error("ladish_client_create() failed.");
827 goto free_alsa_names
;
830 if (!ladish_graph_add_client(vgraph
, vclient
, "Hardware Playback", false))
832 ladish_client_destroy(vclient
);
833 goto free_alsa_names
;
839 { /* non-system client */
840 log_info("non-system client port appeared");
842 if (ladish_client_get_interlink(jack_client
, vclient_uuid
))
844 vclient
= ladish_graph_find_client_by_uuid(vgraph
, vclient_uuid
);
845 ASSERT(vclient
!= NULL
);
851 vclient
= ladish_graph_find_client_by_app(vgraph
, app_uuid
);
854 log_info("Lookup by app uuid failed, attempting lookup by name '%s'", vclient_name
);
861 vclient
= ladish_graph_find_client_by_name(vgraph
, vclient_name
, true);
866 log_info("creating new vclient");
867 if (!ladish_client_create(NULL
, &vclient
))
869 log_error("ladish_client_create() failed.");
870 goto free_alsa_names
;
873 ladish_client_interlink(vclient
, jack_client
);
877 ladish_client_set_app(vclient
, app_uuid
);
880 if (!ladish_graph_add_client(vgraph
, vclient
, vclient_name
, false))
882 log_error("ladish_graph_add_client() failed to add client '%s' to virtual graph", jack_client_name
);
883 ladish_client_destroy(vclient
);
884 goto free_alsa_names
;
889 /* vclient exists but is not interlinked with vclient */
890 /* this can happen when client is created because of a2j port appear */
891 ladish_client_interlink(vclient
, jack_client
);
896 /********************/
897 /* add newly appeared port to the virtual graph */
901 vport_name
= alsa_port_name
;
905 vport_name
= jack_port_name
;
908 if (!ladish_graph_add_port(vgraph
, vclient
, port
, vport_name
, type
, flags
, false))
910 log_error("ladish_graph_add_port() failed.");
911 goto free_alsa_names
;
915 free(a2j_fake_jack_port_name
);
916 free(alsa_client_name
);
917 free(alsa_port_name
);
923 static void maybe_clear_a2j_port_pid(ladish_graph_handle vgraph
, ladish_client_handle jclient
, ladish_port_handle port
)
927 ladish_app_handle app
;
928 ladish_app_supervisor_handle app_supervisor
;
931 if (ladish_virtualizer_is_a2j_client(jclient
) && ladish_port_get_app(port
, app_uuid
))
933 pid
= ladish_port_get_pid(port
);
934 opath
= ladish_graph_get_opath(vgraph
);
935 log_info("releasing reference for pid %d of a2j port in %s", (int)pid
, ladish_graph_get_description(vgraph
));
936 app_supervisor
= ladish_studio_find_app_supervisor(opath
);
937 app
= ladish_app_supervisor_find_app_by_uuid(app_supervisor
, app_uuid
);
938 if (app
!= NULL
&& pid
!= 0)
940 ladish_app_del_pid(app
, pid
);
945 static void port_disappeared(void * context
, uint64_t client_id
, uint64_t port_id
)
947 ladish_client_handle jclient
;
948 ladish_client_handle vclient
;
949 ladish_port_handle port
;
950 ladish_graph_handle vgraph
;
953 log_info("port_disappeared(%"PRIu64
", %"PRIu64
")", client_id
, port_id
);
955 jclient
= ladish_graph_find_client_by_jack_id(virtualizer_ptr
->jack_graph
, client_id
);
958 log_error("Port of unknown JACK client with id %"PRIu64
" disappeared", client_id
);
962 port
= ladish_graph_find_port_by_jack_id(virtualizer_ptr
->jack_graph
, port_id
, true, true);
965 log_error("Unknown JACK port with id %"PRIu64
" disappeared", port_id
);
969 /* find the virtual graph that owns the app that owns the client that owns the disappeared port */
971 vgraph
= ladish_port_get_vgraph(port
);
974 vgraph
= find_link_port_vgraph_by_uuid(virtualizer_ptr
, ladish_graph_get_port_name(virtualizer_ptr
->jack_graph
, port
), NULL
);
977 log_error("Cannot find vgraph for disappeared jmcore port");
983 ladish_graph_remove_port_by_jack_id(virtualizer_ptr
->jack_graph
, port_id
, true, true);
987 maybe_clear_a2j_port_pid(vgraph
, jclient
, port
);
990 ladish_port_set_pid(port
, 0);
992 if (ladish_graph_is_persist(vgraph
)) /* if port is supposed to be persisted */
996 ladish_port_set_jack_id(port
, 0);
997 ladish_graph_hide_port(virtualizer_ptr
->jack_graph
, port
);
1001 ladish_graph_hide_port(vgraph
, port
);
1002 vclient
= ladish_graph_get_port_client(vgraph
, port
);
1003 if (ladish_graph_client_looks_empty(vgraph
, vclient
))
1005 ladish_graph_hide_client(vgraph
, vclient
);
1013 ladish_graph_remove_port(virtualizer_ptr
->jack_graph
, port
);
1018 vclient
= ladish_graph_remove_port(vgraph
, port
);
1019 if (vclient
!= NULL
)
1021 if (ladish_graph_client_is_empty(vgraph
, vclient
))
1023 ladish_graph_remove_client(vgraph
, vclient
);
1024 ladish_client_clear_interlink(jclient
);
1037 const char * old_port_name
,
1038 const char * new_port_name
)
1040 ladish_port_handle port
;
1041 ladish_graph_handle vgraph
;
1043 log_info("port_renamed(%"PRIu64
":%"PRIu64
", '%s', '%s')", client_id
, port_id
, old_port_name
, new_port_name
);
1045 port
= ladish_graph_find_port_by_jack_id(virtualizer_ptr
->jack_graph
, port_id
, true, true);
1048 log_error("Unknown JACK port with id %"PRIu64
" was renamed", port_id
);
1052 /* find the virtual graph that owns the app that owns the client that owns the renamed port */
1053 vgraph
= ladish_port_get_vgraph(port
);
1055 if (!ladish_graph_rename_port(virtualizer_ptr
->jack_graph
, port
, new_port_name
))
1057 log_error("renaming of port in jack graph failed");
1060 if (!ladish_graph_rename_port(vgraph
, port
, new_port_name
))
1062 log_error("renaming of port in virtual graph failed");
1066 static bool ports_connect_request(void * context
, ladish_graph_handle graph_handle
, ladish_port_handle port1
, ladish_port_handle port2
)
1071 ASSERT(ladish_graph_get_opath(graph_handle
)); /* studio or room virtual graph */
1072 log_info("virtualizer: ports connect request");
1074 if (graph_handle
== g_studio
.studio_graph
)
1076 port1_id
= ladish_port_get_jack_id(port1
);
1077 port2_id
= ladish_port_get_jack_id(port2
);
1081 port1_id
= ladish_port_get_jack_id_room(port1
);
1082 port2_id
= ladish_port_get_jack_id_room(port2
);
1085 return graph_proxy_connect_ports(virtualizer_ptr
->jack_graph_proxy
, port1_id
, port2_id
);
1088 static bool ports_disconnect_request(void * context
, ladish_graph_handle graph_handle
, uint64_t connection_id
)
1090 ladish_port_handle port1
;
1091 ladish_port_handle port2
;
1095 ASSERT(ladish_graph_get_opath(graph_handle
)); /* studio or room virtual graph */
1096 log_info("virtualizer: ports disconnect request");
1098 if (!ladish_graph_get_connection_ports(graph_handle
, connection_id
, &port1
, &port2
))
1100 log_error("cannot find ports that are disconnect-requested");
1105 if (graph_handle
== g_studio
.studio_graph
)
1107 port1_id
= ladish_port_get_jack_id(port1
);
1108 port2_id
= ladish_port_get_jack_id(port2
);
1112 port1_id
= ladish_port_get_jack_id_room(port1
);
1113 port2_id
= ladish_port_get_jack_id_room(port2
);
1116 return graph_proxy_disconnect_ports(virtualizer_ptr
->jack_graph_proxy
, port1_id
, port2_id
);
1119 static void ports_connected(void * context
, uint64_t client1_id
, uint64_t port1_id
, uint64_t client2_id
, uint64_t port2_id
)
1121 ladish_port_handle port1
;
1122 ladish_port_handle port2
;
1123 uint64_t connection_id
;
1124 ladish_graph_handle vgraph1
;
1125 ladish_graph_handle vgraph2
;
1127 log_info("ports_connected %"PRIu64
":%"PRIu64
" %"PRIu64
":%"PRIu64
"", client1_id
, port1_id
, client2_id
, port2_id
);
1129 if (!lookup_port(virtualizer_ptr
, port1_id
, &port1
, &vgraph1
))
1134 if (!lookup_port(virtualizer_ptr
, port2_id
, &port2
, &vgraph2
))
1139 if (vgraph1
!= vgraph2
)
1142 log_error("ignoring connection with endpoints in different vgraphs");
1146 ladish_graph_add_connection(virtualizer_ptr
->jack_graph
, port1
, port2
, false);
1148 if (ladish_graph_find_connection(vgraph1
, port1
, port2
, &connection_id
))
1150 log_info("showing hidden virtual connection");
1151 ladish_graph_show_connection(vgraph1
, connection_id
);
1155 log_info("creating new virtual connection");
1156 ladish_graph_add_connection(vgraph1
, port1
, port2
, false);
1160 static void ports_disconnected(void * context
, uint64_t client1_id
, uint64_t port1_id
, uint64_t client2_id
, uint64_t port2_id
)
1162 ladish_port_handle port1
;
1163 ladish_port_handle port2
;
1164 uint64_t connection_id
;
1165 ladish_graph_handle vgraph1
;
1166 ladish_graph_handle vgraph2
;
1168 log_info("ports_disconnected %"PRIu64
":%"PRIu64
" %"PRIu64
":%"PRIu64
"", client1_id
, port1_id
, client2_id
, port2_id
);
1170 if (!lookup_port(virtualizer_ptr
, port1_id
, &port1
, &vgraph1
))
1175 if (!lookup_port(virtualizer_ptr
, port2_id
, &port2
, &vgraph2
))
1180 if (vgraph1
!= vgraph2
)
1183 log_error("ignoring connection with endpoints in different vgraphs");
1187 if (ladish_graph_find_connection(virtualizer_ptr
->jack_graph
, port1
, port2
, &connection_id
))
1189 ladish_graph_remove_connection(virtualizer_ptr
->jack_graph
, connection_id
, true);
1193 log_error("ports %"PRIu64
":%"PRIu64
" and %"PRIu64
":%"PRIu64
" are not connected in the JACK graph", client1_id
, port1_id
, client2_id
, port2_id
);
1196 if (ladish_graph_find_connection(vgraph1
, port1
, port2
, &connection_id
))
1198 ladish_graph_remove_connection(vgraph1
, connection_id
, false);
1202 log_error("ports %"PRIu64
":%"PRIu64
" and %"PRIu64
":%"PRIu64
" are not connected in the virtual graph", client1_id
, port1_id
, client2_id
, port2_id
);
1206 #undef virtualizer_ptr
1209 ladish_virtualizer_create(
1210 graph_proxy_handle jack_graph_proxy
,
1211 ladish_graph_handle jack_graph
,
1212 ladish_virtualizer_handle
* handle_ptr
)
1214 struct virtualizer
* virtualizer_ptr
;
1216 virtualizer_ptr
= malloc(sizeof(struct virtualizer
));
1217 if (virtualizer_ptr
== NULL
)
1219 log_error("malloc() failed for struct virtualizer");
1223 virtualizer_ptr
->jack_graph_proxy
= jack_graph_proxy
;
1224 virtualizer_ptr
->jack_graph
= jack_graph
;
1225 virtualizer_ptr
->system_client_id
= 0;
1226 virtualizer_ptr
->system_midi_client_id
= 0;
1227 virtualizer_ptr
->our_clients_count
= 0;
1229 if (!graph_proxy_attach(
1234 NULL
, /* jackdbus does not have client rename functionality (yet) */
1240 ports_disconnected
))
1242 free(virtualizer_ptr
);
1246 *handle_ptr
= (ladish_virtualizer_handle
)virtualizer_ptr
;
1250 #define virtualizer_ptr ((struct virtualizer *)handle)
1253 ladish_virtualizer_set_graph_connection_handlers(
1254 ladish_virtualizer_handle handle
,
1255 ladish_graph_handle graph
)
1257 ladish_graph_set_connection_handlers(graph
, virtualizer_ptr
, ports_connect_request
, ports_disconnect_request
);
1261 ladish_virtualizer_get_our_clients_count(
1262 ladish_virtualizer_handle handle
)
1264 return virtualizer_ptr
->our_clients_count
;
1267 static bool app_has_a2j_ports(ladish_graph_handle jack_graph
, const uuid_t app_uuid
)
1269 ladish_client_handle a2jclient
;
1271 a2jclient
= ladish_graph_find_client_by_uuid(jack_graph
, g_a2j_uuid
);
1272 if (a2jclient
== NULL
)
1277 return ladish_graph_client_has_visible_app_port(jack_graph
, a2jclient
, app_uuid
);
1281 ladish_virtualizer_is_hidden_app(
1282 ladish_graph_handle jack_graph
,
1283 const uuid_t app_uuid
,
1284 const char * app_name
)
1286 ladish_client_handle jclient
;
1287 ladish_graph_handle vgraph
;
1288 uuid_t vclient_uuid
;
1289 ladish_client_handle vclient
;
1291 //ladish_graph_dump(g_studio.jack_graph);
1293 if (app_has_a2j_ports(jack_graph
, app_uuid
))
1295 log_info("app '%s' still has a2j ports", app_name
);
1299 jclient
= ladish_graph_find_client_by_app(jack_graph
, app_uuid
);
1300 if (jclient
== NULL
)
1302 log_info("App without JACK client is treated as hidden one");
1306 ASSERT(!ladish_virtualizer_is_a2j_client(jclient
)); /* a2j client has no app associated */
1308 vgraph
= ladish_client_get_vgraph(jclient
);
1315 //ladish_graph_dump(vgraph);
1317 if (!ladish_graph_client_looks_empty(jack_graph
, jclient
) ||
1318 !ladish_graph_client_is_hidden(jack_graph
, jclient
))
1323 if (!ladish_client_get_interlink(jclient
, vclient_uuid
))
1325 if (ladish_graph_client_is_empty(jack_graph
, jclient
))
1327 log_info("jack client of app '%s' has no interlinked vgraph client and no ports", app_name
);
1331 log_error("jack client of app '%s' has no interlinked vgraph client", app_name
);
1337 vclient
= ladish_graph_find_client_by_uuid(vgraph
, vclient_uuid
);
1338 if (vclient
== NULL
)
1344 if (!ladish_graph_client_looks_empty(vgraph
, vclient
))
1349 ASSERT(ladish_graph_client_is_hidden(vgraph
, vclient
)); /* vclients are automatically hidden when they start looking empty (on port disappear) */
1353 struct app_remove_context
1356 const char * app_name
;
1359 #define app_info_ptr ((struct app_remove_context *)context)
1365 ladish_graph_handle graph_handle
,
1366 bool UNUSED(hidden
),
1367 void * UNUSED(client_iteration_context_ptr
),
1368 ladish_client_handle
UNUSED(client_handle
),
1369 const char * client_name
,
1370 ladish_port_handle port_handle
,
1371 const char * port_name
,
1373 uint32_t port_flags
)
1375 ladish_graph_handle vgraph
;
1376 ladish_client_handle vclient
;
1378 if (!ladish_port_belongs_to_app(port_handle
, app_info_ptr
->app_uuid
))
1383 //log_info("removing port '%s':'%s' (JACK) of app '%s'", client_name, port_name, app_info_ptr->app_name);
1385 vgraph
= ladish_port_get_vgraph(port_handle
);
1388 log_error("port '%s':'%s' of app '%s' has no vgraph", client_name
, port_name
, app_info_ptr
->app_name
);
1393 vclient
= ladish_graph_get_port_client(vgraph
, port_handle
);
1396 log_error("app port '%s':'%s' not found in vgraph '%s'", client_name
, port_name
, ladish_graph_get_description(vgraph
));
1402 "removing %s %s port %p of app '%s' ('%s':'%s' in %s)",
1403 port_type
== JACKDBUS_PORT_TYPE_AUDIO
? "audio" : "midi",
1404 JACKDBUS_PORT_IS_INPUT(port_flags
) ? "input" : "output",
1406 app_info_ptr
->app_name
,
1407 ladish_graph_get_client_name(vgraph
, vclient
),
1408 ladish_graph_get_port_name(vgraph
, port_handle
),
1409 ladish_graph_get_description(vgraph
));
1411 ladish_graph_remove_port(graph_handle
, port_handle
);
1412 ladish_graph_remove_port(vgraph
, port_handle
);
1420 ladish_virtualizer_remove_app(
1421 ladish_graph_handle jack_graph
,
1422 const uuid_t app_uuid
,
1423 const char * app_name
)
1425 ladish_client_handle jclient
;
1426 ladish_graph_handle vgraph
;
1427 uuid_t vclient_uuid
;
1428 ladish_client_handle vclient
;
1430 struct app_remove_context ctx
;
1432 //ladish_graph_dump(g_studio.jack_graph);
1434 uuid_copy(ctx
.app_uuid
, app_uuid
);
1435 ctx
.app_name
= app_name
;
1437 ladish_graph_iterate_nodes(jack_graph
, &ctx
, NULL
, remove_app_port
, NULL
);
1439 jclient
= ladish_graph_find_client_by_app(jack_graph
, app_uuid
);
1440 if (jclient
== NULL
)
1442 log_info("removing app without JACK client");
1446 ASSERT(!ladish_virtualizer_is_a2j_client(jclient
)); /* a2j client has no app associated */
1448 vgraph
= ladish_client_get_vgraph(jclient
);
1455 //ladish_graph_dump(vgraph);
1457 /* check whether the client is empty because this cannot
1458 be checked later because the client was removed
1459 (see where is_empty is used) */
1460 is_empty
= ladish_graph_client_is_empty(jack_graph
, jclient
);
1462 ladish_graph_remove_client(jack_graph
, jclient
);
1464 if (!ladish_client_get_interlink(jclient
, vclient_uuid
))
1468 /* jack client without ports and thus without vgraph client */
1472 log_error("jack client of app '%s' has no interlinked vgraph client", app_name
);
1473 ladish_graph_dump(g_studio
.jack_graph
);
1474 ladish_graph_dump(vgraph
);
1479 vclient
= ladish_graph_find_client_by_uuid(vgraph
, vclient_uuid
);
1480 if (vclient
== NULL
)
1486 ladish_graph_remove_client(vgraph
, vclient
);
1487 ladish_graph_dump(g_studio
.jack_graph
);
1488 ladish_graph_dump(vgraph
);
1492 ladish_virtualizer_destroy(
1493 ladish_virtualizer_handle handle
)
1495 log_info("ladish_virtualizer_destroy() called");
1497 graph_proxy_detach((graph_proxy_handle
)handle
, virtualizer_ptr
);
1498 free(virtualizer_ptr
);
1501 #undef virtualizer_ptr
1503 #define vgraph ((ladish_graph_handle)vgraph_context)
1505 ladish_virtualizer_rename_app(
1506 void * vgraph_context
,
1508 const char * UNUSED(old_name
),
1509 const char * new_app_name
)
1511 ladish_client_handle client
;
1513 client
= ladish_graph_find_client_by_app(vgraph
, uuid
);
1516 ladish_graph_rename_client(vgraph
, client
, new_app_name
);
1519 client
= ladish_graph_find_client_by_app(g_studio
.jack_graph
, uuid
);
1522 ladish_graph_rename_client(g_studio
.jack_graph
, client
, new_app_name
);
1528 ladish_virtualizer_is_system_client(
1531 if (uuid_compare(uuid
, g_system_capture_uuid
) == 0)
1536 if (uuid_compare(uuid
, g_system_playback_uuid
) == 0)
1544 bool ladish_virtualizer_is_a2j_client(ladish_client_handle jclient
)
1546 uuid_t jclient_uuid
;
1548 ladish_client_get_uuid(jclient
, jclient_uuid
);
1549 return uuid_compare(jclient_uuid
, g_a2j_uuid
) == 0;
1554 move_capture_port_callback(
1556 ladish_graph_handle graph_handle
,
1557 bool UNUSED(hidden
),
1558 ladish_client_handle client_handle
,
1559 const char * UNUSED(client_name
),
1560 ladish_port_handle port_handle
,
1561 const char * UNUSED(port_name
),
1562 uint32_t UNUSED(port_type
),
1563 uint32_t port_flags
)
1565 ASSERT(client_handle
!= context
); /* source and destination clients must be differ */
1567 if (JACKDBUS_PORT_IS_INPUT(port_flags
))
1569 ladish_graph_move_port(graph_handle
, port_handle
, context
);
1575 bool ladish_virtualizer_split_client(ladish_graph_handle vgraph
, uint64_t client_id
)
1577 ladish_client_handle vclient1
;
1578 ladish_client_handle vclient2
;
1581 vclient1
= ladish_graph_find_client_by_id(vgraph
, client_id
);
1582 if (vclient1
== NULL
)
1584 log_error("Cannot find client %"PRIu64
" in %s", client_id
, ladish_graph_get_description(vgraph
));
1588 name
= ladish_graph_get_client_name(vgraph
, vclient1
);
1590 if (!ladish_client_create(NULL
, &vclient2
))
1592 log_error("ladish_client_create() failed.");
1596 ladish_client_interlink_copy(vclient2
, vclient1
);
1597 ladish_client_copy_app(vclient2
, vclient1
);
1599 if (!ladish_graph_add_client(vgraph
, vclient2
, name
, false))
1601 log_error("ladish_graph_add_client() failed to add client '%s' to virtual graph", name
);
1602 ladish_client_destroy(vclient2
);
1606 return ladish_graph_interate_client_ports(vgraph
, vclient1
, vclient2
, move_capture_port_callback
);
1613 ladish_graph_handle graph_handle
,
1614 bool UNUSED(hidden
),
1615 ladish_client_handle client_handle
,
1616 const char * UNUSED(client_name
),
1617 ladish_port_handle port_handle
,
1618 const char * UNUSED(port_name
),
1619 uint32_t UNUSED(port_type
),
1620 uint32_t UNUSED(port_flags
))
1622 ASSERT(client_handle
!= context
); /* source and destination clients must be differ */
1623 ladish_graph_move_port(graph_handle
, port_handle
, context
);
1628 ladish_virtualizer_join_clients(
1629 ladish_graph_handle vgraph
,
1630 uint64_t client1_id
,
1631 uint64_t client2_id
)
1633 ladish_client_handle vclient1
;
1634 ladish_client_handle vclient2
;
1636 if (client1_id
== client2_id
)
1638 log_error("Cannot join same client");
1642 vclient1
= ladish_graph_find_client_by_id(vgraph
, client1_id
);
1643 if (vclient1
== NULL
)
1645 log_error("Cannot find client %"PRIu64
" in %s", client1_id
, ladish_graph_get_description(vgraph
));
1649 vclient2
= ladish_graph_find_client_by_id(vgraph
, client2_id
);
1650 if (vclient2
== NULL
)
1652 log_error("Cannot find client %"PRIu64
" in %s", client2_id
, ladish_graph_get_description(vgraph
));
1656 ladish_graph_interate_client_ports(vgraph
, vclient2
, vclient1
, move_port_callback
);
1658 ladish_graph_remove_client(vgraph
, vclient2
);