ladishd: Deassociate pids of disappearing clients. Fix for #119
[ladish.git] / daemon / virtualizer.c
blob67da39717173f97ac79e96bc5249f766d7fc9e21
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
2 /*
3 * LADI Session Handler (ladish)
5 * Copyright (C) 2009, 2010 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"
31 #include "procfs.h"
32 #include "app_supervisor.h"
33 #include "studio_internal.h"
34 #include "../common/catdup.h"
35 #include "room.h"
36 #include "studio.h"
37 #include "../alsapid/alsapid.h"
39 struct virtualizer
41 graph_proxy_handle jack_graph_proxy;
42 ladish_graph_handle jack_graph;
43 uint64_t system_client_id;
44 unsigned int our_clients_count;
47 /* 47c1cd18-7b21-4389-bec4-6e0658e1d6b1 */
48 UUID_DEFINE(g_system_capture_uuid,0x47,0xC1,0xCD,0x18,0x7B,0x21,0x43,0x89,0xBE,0xC4,0x6E,0x06,0x58,0xE1,0xD6,0xB1);
50 /* b2a0bb06-28d8-4bfe-956e-eb24378f9629 */
51 UUID_DEFINE(g_system_playback_uuid,0xB2,0xA0,0xBB,0x06,0x28,0xD8,0x4B,0xFE,0x95,0x6E,0xEB,0x24,0x37,0x8F,0x96,0x29);
53 /* be23a242-e2b2-11de-b795-002618af5e42 */
54 UUID_DEFINE(g_a2j_uuid,0xBE,0x23,0xA2,0x42,0xE2,0xB2,0x11,0xDE,0xB7,0x95,0x00,0x26,0x18,0xAF,0x5E,0x42);
56 struct app_find_context
58 pid_t pid;
59 ladish_graph_handle graph;
60 ladish_app_handle app;
63 #define app_find_context_ptr ((struct app_find_context *)context)
65 static bool lookup_app_in_supervisor(void * context, ladish_graph_handle graph, ladish_app_supervisor_handle app_supervisor)
67 pid_t pid;
68 ladish_app_handle app;
70 /* we stop iteration when app is found */
71 ASSERT(app_find_context_ptr->app == NULL && app_find_context_ptr->graph == NULL);
73 //log_info("checking app supervisor \"%s\" for pid %llu", ladish_app_supervisor_get_name(app_supervisor), (unsigned long long)pid);
75 pid = app_find_context_ptr->pid;
78 app = ladish_app_supervisor_find_app_by_pid(app_supervisor, pid);
79 if (app != NULL)
80 break;
82 pid = (pid_t)procfs_get_process_parent((unsigned long long)pid);
83 #if 0
84 if (pid != 0)
86 log_info("parent pid %llu", (unsigned long long)pid);
88 #endif
90 while (pid != 0);
92 if (app == NULL)
93 { /* app not found in current supervisor */
94 return true; /* continue app supervisor iteration */
97 app_find_context_ptr->app = app;
98 app_find_context_ptr->graph = graph;
100 return false; /* stop app supervisor iteration */
103 #undef app_find_context_ptr
105 static ladish_app_handle ladish_virtualizer_find_app_by_pid(struct virtualizer * virtualizer_ptr, pid_t pid, ladish_graph_handle * graph_ptr)
107 struct app_find_context context;
109 context.pid = pid;
110 context.app = NULL;
111 context.graph = NULL;
113 ladish_studio_iterate_virtual_graphs(&context, lookup_app_in_supervisor);
115 if (context.app == NULL)
116 { /* app not found */
117 ASSERT(context.graph == NULL);
118 return NULL;
121 ASSERT(context.graph != NULL);
122 *graph_ptr = context.graph;
124 return context.app;
127 struct find_link_port_context
129 uuid_t uuid;
130 uint64_t jack_id;
131 ladish_port_handle port;
132 ladish_graph_handle graph;
135 #define find_link_port_context_ptr ((struct find_link_port_context *)context)
137 static bool find_link_port_vgraph_callback_by_uuid(void * context, ladish_graph_handle graph, ladish_app_supervisor_handle app_supervisor)
139 ladish_port_handle port;
141 port = ladish_graph_find_port_by_uuid(graph, find_link_port_context_ptr->uuid, true, NULL);
142 if (port != NULL)
144 find_link_port_context_ptr->port = port;
145 find_link_port_context_ptr->graph = graph;
146 return false;
149 return true; /* continue vgraph iteration */
152 static bool find_link_port_vgraph_callback_by_jack_id(void * context, ladish_graph_handle graph, ladish_app_supervisor_handle app_supervisor)
154 ladish_port_handle port;
155 bool room;
157 log_info("searching link port with jack id %"PRIu64" in graph %s", find_link_port_context_ptr->jack_id, ladish_graph_get_description(graph));
159 room = graph != g_studio.studio_graph;
161 port = ladish_graph_find_port_by_jack_id(graph, find_link_port_context_ptr->jack_id, room, !room);
162 if (port != NULL)
164 find_link_port_context_ptr->port = port;
165 find_link_port_context_ptr->graph = graph;
166 return false;
169 return true; /* continue vgraph iteration */
172 #undef find_link_port_context_ptr
174 static ladish_graph_handle find_link_port_vgraph_by_uuid(struct virtualizer * virtualizer_ptr, const char * port_name, ladish_port_handle * port_ptr)
176 struct find_link_port_context context;
178 uuid_parse(port_name, context.uuid);
179 context.graph = NULL;
180 context.port = NULL;
182 ladish_studio_iterate_virtual_graphs(&context, find_link_port_vgraph_callback_by_uuid);
184 if (port_ptr != NULL && context.graph != NULL)
186 *port_ptr = context.port;
189 return context.graph;
192 static ladish_graph_handle find_link_port_vgraph_by_jack_id(struct virtualizer * virtualizer_ptr, uint64_t jack_id, ladish_port_handle * port_ptr)
194 struct find_link_port_context context;
196 context.jack_id = jack_id;
197 context.graph = NULL;
198 context.port = NULL;
200 ladish_studio_iterate_virtual_graphs(&context, find_link_port_vgraph_callback_by_jack_id);
202 if (port_ptr != NULL && context.graph != NULL)
204 *port_ptr = context.port;
207 return context.graph;
210 static
211 bool
212 lookup_port(
213 struct virtualizer * virtualizer_ptr,
214 uint64_t port_id,
215 ladish_port_handle * port_ptr,
216 ladish_graph_handle * vgraph_ptr)
218 ladish_port_handle port;
219 ladish_graph_handle vgraph;
221 port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
222 if (port == NULL)
224 log_error("Unknown JACK port with id %"PRIu64" (dis)connected", port_id);
225 return false;
228 vgraph = ladish_port_get_vgraph(port);
229 if (vgraph == NULL)
231 vgraph = find_link_port_vgraph_by_jack_id(virtualizer_ptr, port_id, NULL);
232 if (vgraph == NULL)
234 log_error("Cannot find vgraph for (dis)connected jmcore port");
235 return false;
238 log_info("link port found in graph %s", ladish_graph_get_description(vgraph));
241 *port_ptr = port;
242 *vgraph_ptr = vgraph;
243 return true;
246 #define virtualizer_ptr ((struct virtualizer *)context)
248 static void clear(void * context)
250 log_info("clear");
253 static void client_appeared(void * context, uint64_t id, const char * jack_name)
255 ladish_client_handle client;
256 const char * a2j_name;
257 bool is_a2j;
258 ladish_app_handle app;
259 uuid_t app_uuid;
260 const char * name;
261 pid_t pid;
262 ladish_graph_handle graph;
263 bool jmcore;
265 log_info("client_appeared(%"PRIu64", %s)", id, jack_name);
267 a2j_name = a2j_proxy_get_jack_client_name_cached();
268 is_a2j = a2j_name != NULL && strcmp(a2j_name, jack_name) == 0;
270 name = jack_name;
271 app = NULL;
272 graph = NULL;
273 jmcore = false;
275 if (!graph_proxy_get_client_pid(virtualizer_ptr->jack_graph_proxy, id, &pid))
277 log_info("client %"PRIu64" pid is unknown", id);
279 else
281 log_info("client pid is %"PRId64, (int64_t)pid);
283 if (pid != 0) /* skip internal clients that will match the pending clients in the graph, both have zero pid */
285 jmcore = pid == jmcore_proxy_get_pid_cached();
286 if (jmcore)
288 log_info("jmcore client appeared");
290 else
292 app = ladish_virtualizer_find_app_by_pid(virtualizer_ptr, pid, &graph);
293 if (app != NULL)
295 ladish_app_get_uuid(app, app_uuid);
296 ASSERT(!uuid_is_null(app_uuid));
297 name = ladish_app_get_name(app);
298 log_info("app name is '%s'", name);
304 if (!jmcore)
306 if (is_a2j)
308 client = ladish_graph_find_client_by_uuid(virtualizer_ptr->jack_graph, g_a2j_uuid);
310 else
312 client = ladish_graph_find_client_by_app(virtualizer_ptr->jack_graph, app_uuid);
313 if (client == NULL)
315 log_info("Lookup by app uuid failed, attempting lookup by name '%s'", name);
316 client = ladish_graph_find_client_by_name(virtualizer_ptr->jack_graph, name, true);
320 if (client != NULL)
322 log_info("found existing client");
323 if (ladish_client_get_jack_id(client) != 0)
325 log_error("Ignoring client with duplicate name '%s' ('%s')", name, jack_name);
326 goto exit;
329 ladish_client_set_jack_id(client, id);
330 ladish_graph_show_client(virtualizer_ptr->jack_graph, client);
331 goto done;
335 if (!ladish_client_create(is_a2j ? g_a2j_uuid : NULL, &client))
337 log_error("ladish_client_create() failed. Ignoring client %"PRIu64" (%s)", id, jack_name);
338 goto exit;
341 ladish_client_set_jack_id(client, id);
343 if (!ladish_graph_add_client(virtualizer_ptr->jack_graph, client, name, false))
345 log_error("ladish_graph_add_client() failed to add client %"PRIu64" (%s) to JACK graph", id, name);
346 ladish_client_destroy(client);
347 goto exit;
350 done:
351 if (strcmp(jack_name, "system") == 0)
353 virtualizer_ptr->system_client_id = id;
356 if (app != NULL)
358 /* interlink client and app */
359 ladish_app_add_pid(app, pid);
360 ladish_client_set_pid(client, pid);
361 ladish_client_set_app(client, app_uuid);
363 ASSERT(graph);
364 ladish_client_set_vgraph(client, graph);
365 virtualizer_ptr->our_clients_count++;
367 else if (jmcore)
369 ladish_client_set_pid(client, pid);
370 ASSERT(ladish_client_get_vgraph(client) == NULL);
372 else
374 /* unknown and internal clients appear in the studio graph */
375 ladish_client_set_vgraph(client, g_studio.studio_graph);
378 exit:
379 return;
382 static void client_disappeared(void * context, uint64_t id)
384 ladish_client_handle client;
385 pid_t pid;
386 uuid_t app_uuid;
387 ladish_app_handle app;
388 ladish_graph_handle vgraph;
390 log_info("client_disappeared(%"PRIu64")", id);
392 client = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, id);
393 if (client == NULL)
395 log_error("Unknown JACK client with id %"PRIu64" disappeared", id);
396 return;
399 log_info("client disappeared: '%s'", ladish_graph_get_client_name(virtualizer_ptr->jack_graph, client));
401 vgraph = ladish_client_get_vgraph(client);
403 pid = ladish_client_get_pid(client);
404 if (ladish_client_get_app(client, app_uuid))
406 virtualizer_ptr->our_clients_count--;
407 app = ladish_studio_find_app_by_uuid(app_uuid);
408 if (app != NULL)
410 ladish_app_del_pid(app, pid);
412 else
414 log_error("app of disappearing client %"PRIu64" not found. pid is %"PRIu64, id, (uint64_t)pid);
415 ASSERT_NO_PASS;
419 if (id == virtualizer_ptr->system_client_id)
421 virtualizer_ptr->system_client_id = 0;
424 if (vgraph != NULL && ladish_graph_is_persist(vgraph)) /* if client is supposed to be persisted */
426 ladish_client_set_jack_id(client, 0);
427 ladish_graph_hide_client(virtualizer_ptr->jack_graph, client);
429 else
431 ladish_graph_remove_client(virtualizer_ptr->jack_graph, client);
432 ladish_client_destroy(client);
433 /* no need to clear vclient interlink because it either does not exist (vgraph is NULL) or
434 * it will be destroyed before it is accessed (persist flag is cleared on room deletion) */
438 static
439 void
440 port_appeared(
441 void * context,
442 uint64_t client_id,
443 uint64_t port_id,
444 const char * real_jack_port_name,
445 bool is_input,
446 bool is_terminal,
447 bool is_midi)
449 ladish_client_handle jack_client;
450 ladish_client_handle vclient;
451 ladish_port_handle port;
452 uint32_t type;
453 uint32_t flags;
454 const char * jack_client_name;
455 const char * vclient_name;
456 bool is_a2j;
457 uuid_t vclient_uuid;
458 pid_t pid;
459 ladish_app_handle app;
460 bool has_app;
461 uuid_t app_uuid;
462 char * alsa_client_name;
463 char * alsa_port_name;
464 char * a2j_fake_jack_port_name = NULL;
465 uint32_t alsa_client_id;
466 const char * jack_port_name;
467 const char * vport_name;
468 ladish_graph_handle vgraph;
470 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");
472 type = is_midi ? JACKDBUS_PORT_TYPE_MIDI : JACKDBUS_PORT_TYPE_AUDIO;
473 flags = is_input ? JACKDBUS_PORT_FLAG_INPUT : JACKDBUS_PORT_FLAG_OUTPUT;
474 if (is_terminal)
476 flags |= JACKDBUS_PORT_FLAG_TERMINAL;
479 /********************/
480 /* gather info about the appeared port */
482 jack_client = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, client_id);
483 if (jack_client == NULL)
485 log_error("Port of unknown JACK client with id %"PRIu64" appeared", client_id);
486 goto exit;
489 has_app = ladish_client_get_app(jack_client, app_uuid);
491 /* find the virtual graph that owns the app that owns the client that owns the appeared port */
492 vgraph = ladish_client_get_vgraph(jack_client);
493 if (vgraph == NULL)
495 vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, real_jack_port_name, &port);
496 if (vgraph == NULL)
498 log_error("Cannot find vgraph for appeared jmcore port '%s'", real_jack_port_name);
499 goto exit;
502 /* jmcore port appeared */
504 log_info("jmcore port appeared in vgraph %s", ladish_graph_get_description(vgraph));
506 if (!ladish_graph_add_port(virtualizer_ptr->jack_graph, jack_client, port, real_jack_port_name, type, flags, false))
508 log_error("ladish_graph_add_port() failed.");
509 goto exit;
512 if (vgraph == g_studio.studio_graph)
514 ladish_port_set_jack_id(port, port_id);
516 else
518 ladish_port_set_jack_id_room(port, port_id);
521 vclient = ladish_graph_get_port_client(vgraph, port);
522 if (vclient == NULL)
524 log_error("link port client not found in vgraph %s", ladish_graph_get_description(vgraph));
525 ASSERT_NO_PASS;
526 goto exit;
529 ladish_graph_show_port(vgraph, port);
530 goto exit;
532 else
534 //log_info("Port of virtual graph '%s'", ladish_graph_get_description(vgraph));
537 jack_client_name = ladish_graph_get_client_name(virtualizer_ptr->jack_graph, jack_client);
539 is_a2j = ladish_virtualizer_is_a2j_client(jack_client);
540 if (is_a2j)
542 log_info("a2j port appeared");
543 if (!a2j_proxy_map_jack_port(real_jack_port_name, &alsa_client_name, &alsa_port_name, &alsa_client_id))
545 is_a2j = false;
546 alsa_client_name = catdup("FAILED ", jack_client_name);
547 if (alsa_client_name == NULL)
549 log_error("catdup failed to duplicate a2j jack client name after map failure");
550 goto exit;
553 alsa_port_name = strdup(real_jack_port_name);
554 if (alsa_port_name == NULL)
556 log_error("catdup failed to duplicate a2j jack port name after map failure");
557 free(alsa_client_name);
558 goto exit;
561 vclient_name = alsa_client_name;
563 else
565 log_info("a2j: '%s':'%s' (%"PRIu32")", alsa_client_name, alsa_port_name, alsa_client_id);
566 vclient_name = alsa_client_name;
567 if (alsapid_get_pid(alsa_client_id, &pid))
569 log_info("ALSA client pid is %lld", (long long)pid);
571 app = ladish_virtualizer_find_app_by_pid(virtualizer_ptr, pid, &vgraph);
572 if (app != NULL)
574 ladish_app_get_uuid(app, app_uuid);
575 ASSERT(!uuid_is_null(app_uuid));
576 vclient_name = ladish_app_get_name(app);
577 has_app = true;
578 log_info("ALSA app name is '%s'", vclient_name);
581 else
583 log_error("UNKNOWN ALSA client pid");
587 a2j_fake_jack_port_name = catdup4(vclient_name, is_input ? " (playback)" : " (capture)", ": ", alsa_port_name);
588 if (a2j_fake_jack_port_name == NULL)
590 log_error("catdup4() failed");
591 goto free_alsa_names;
594 jack_port_name = a2j_fake_jack_port_name;
596 else
598 vclient_name = jack_client_name;
599 jack_port_name = real_jack_port_name;
602 /********************/
604 /* search (by name) the appeared port in jack graph
605 * if found - show it in both graphs.
606 * if not found - create new port and add it to the jack graph.
607 * Then process to adding it to virtual graph */
609 port = ladish_graph_find_port_by_name(virtualizer_ptr->jack_graph, jack_client, jack_port_name, vgraph);
610 if (port != NULL)
612 log_info("found existing port %p", port);
614 if (ladish_port_get_jack_id(port) != 0)
616 log_error("Ignoring duplicate JACK port '%s':'%s'", jack_client_name, jack_port_name);
617 goto free_alsa_names;
620 ladish_port_set_jack_id(port, port_id);
621 ladish_graph_adjust_port(virtualizer_ptr->jack_graph, port, type, flags);
622 ladish_graph_show_port(virtualizer_ptr->jack_graph, port);
624 vclient = ladish_graph_get_port_client(vgraph, port);
625 if (vclient == NULL)
627 log_error("JACK port not found in virtual graph '%s'", ladish_graph_get_description(vgraph));
628 ladish_graph_dump(g_studio.jack_graph);
629 ladish_graph_dump(vgraph);
630 ASSERT_NO_PASS;
631 goto free_alsa_names;
634 /* for normal ports, one can find the app_uuid through the jack client,
635 but for a2j ports the jack client is shared between graphs */
636 if (has_app)
638 ladish_port_set_app(port, app_uuid);
641 ladish_client_set_jack_id(vclient, client_id);
642 ladish_graph_adjust_port(vgraph, port, type, flags);
643 ladish_graph_show_port(vgraph, port);
644 goto free_alsa_names;
647 if (!ladish_port_create(NULL, false, &port))
649 log_error("ladish_port_create() failed.");
650 goto free_alsa_names;
653 /* set port jack id so invisible connections to/from it can be restored */
654 ladish_port_set_jack_id(port, port_id);
656 /* for normal ports, one can find the vgraph and app_uuid through the jack client,
657 but for a2j ports the jack client is shared between graphs */
658 ladish_port_set_vgraph(port, vgraph);
659 if (has_app)
661 ladish_port_set_app(port, app_uuid);
664 if (!ladish_graph_add_port(virtualizer_ptr->jack_graph, jack_client, port, jack_port_name, type, flags, false))
666 log_error("ladish_graph_add_port() failed.");
667 ladish_port_destroy(port);
668 goto free_alsa_names;
671 /********************/
672 /* find/create the virtual client where port will be added */
674 if (is_a2j)
676 vclient = ladish_graph_find_client_by_name(vgraph, vclient_name, false);
677 if (vclient == NULL)
679 if (!ladish_client_create(NULL, &vclient))
681 log_error("ladish_client_create() failed.");
682 goto free_alsa_names;
685 if (has_app)
687 ladish_client_set_app(vclient, app_uuid);
690 if (!ladish_graph_add_client(vgraph, vclient, vclient_name, false))
692 log_error("ladish_graph_add_client() failed.");
693 ladish_client_destroy(vclient);
694 goto free_alsa_names;
698 else if (client_id == virtualizer_ptr->system_client_id)
700 log_info("system client port appeared");
702 if (!is_input)
703 { /* output capture port */
705 vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_capture_uuid);
706 if (vclient == NULL)
708 if (!ladish_client_create(g_system_capture_uuid, &vclient))
710 log_error("ladish_client_create() failed.");
711 goto free_alsa_names;
714 if (!ladish_graph_add_client(vgraph, vclient, "Hardware Capture", false))
716 log_error("ladish_graph_add_client() failed.");
717 ladish_client_destroy(vclient);
718 goto free_alsa_names;
722 else
723 { /* input playback port */
724 vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_playback_uuid);
725 if (vclient == NULL)
727 if (!ladish_client_create(g_system_playback_uuid, &vclient))
729 log_error("ladish_client_create() failed.");
730 goto free_alsa_names;
733 if (!ladish_graph_add_client(vgraph, vclient, "Hardware Playback", false))
735 ladish_client_destroy(vclient);
736 goto free_alsa_names;
741 else
742 { /* non-system client */
743 log_info("non-system client port appeared");
745 if (ladish_client_get_interlink(jack_client, vclient_uuid))
747 vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid);
748 ASSERT(vclient != NULL);
750 else
752 log_info("creating new vclient");
753 if (!ladish_client_create(NULL, &vclient))
755 log_error("ladish_client_create() failed.");
756 goto free_alsa_names;
759 ladish_client_interlink(vclient, jack_client);
761 if (has_app)
763 ladish_client_set_app(vclient, app_uuid);
766 if (!ladish_graph_add_client(vgraph, vclient, vclient_name, false))
768 log_error("ladish_graph_add_client() failed to add client '%s' to virtual graph", jack_client_name);
769 ladish_client_destroy(vclient);
770 goto free_alsa_names;
775 /********************/
776 /* add newly appeared port to the virtual graph */
778 if (is_a2j)
780 vport_name = alsa_port_name;
782 else
784 vport_name = jack_port_name;
787 if (!ladish_graph_add_port(vgraph, vclient, port, vport_name, type, flags, false))
789 log_error("ladish_graph_add_port() failed.");
790 goto free_alsa_names;
793 free_alsa_names:
794 if (a2j_fake_jack_port_name != NULL)
796 free(a2j_fake_jack_port_name);
799 if (is_a2j)
801 free(alsa_client_name);
802 free(alsa_port_name);
805 exit:
806 return;
809 static void port_disappeared(void * context, uint64_t client_id, uint64_t port_id)
811 ladish_client_handle jclient;
812 ladish_client_handle vclient;
813 ladish_port_handle port;
814 ladish_graph_handle vgraph;
815 bool jmcore;
817 log_info("port_disappeared(%"PRIu64", %"PRIu64")", client_id, port_id);
819 jclient = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, client_id);
820 if (jclient == NULL)
822 log_error("Port of unknown JACK client with id %"PRIu64" disappeared", client_id);
823 return;
826 port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
827 if (port == NULL)
829 log_error("Unknown JACK port with id %"PRIu64" disappeared", port_id);
830 return;
833 /* find the virtual graph that owns the app that owns the client that owns the disappeared port */
834 jmcore = false;
835 vgraph = ladish_port_get_vgraph(port);
836 if (vgraph == NULL)
838 vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, ladish_graph_get_port_name(virtualizer_ptr->jack_graph, port), NULL);
839 if (vgraph == NULL)
841 log_error("Cannot find vgraph for disappeared jmcore port");
842 ASSERT_NO_PASS;
843 return;
846 jmcore = true;
847 ladish_graph_remove_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
850 if (ladish_graph_is_persist(vgraph)) /* if port is supposed to be persisted */
852 if (!jmcore)
854 ladish_port_set_jack_id(port, 0);
855 ladish_graph_hide_port(virtualizer_ptr->jack_graph, port);
857 if (vgraph != NULL)
859 ladish_graph_hide_port(vgraph, port);
860 vclient = ladish_graph_get_port_client(vgraph, port);
861 if (ladish_graph_client_looks_empty(vgraph, vclient))
863 ladish_graph_hide_client(vgraph, vclient);
867 else
869 if (!jmcore)
871 ladish_graph_remove_port(virtualizer_ptr->jack_graph, port);
874 if (vgraph != NULL)
876 vclient = ladish_graph_remove_port(vgraph, port);
877 if (vclient != NULL)
879 if (ladish_graph_client_is_empty(vgraph, vclient))
881 ladish_graph_remove_client(vgraph, vclient);
882 ladish_client_clear_interlink(jclient);
889 static void port_renamed(void * context, uint64_t client_id, uint64_t port_id, const char * old_port_name, const char * new_port_name)
891 ladish_port_handle port;
892 ladish_graph_handle vgraph;
894 log_info("port_renamed(%"PRIu64", '%s', '%s')", port_id, old_port_name, new_port_name);
896 port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
897 if (port == NULL)
899 log_error("Unknown JACK port with id %"PRIu64" was renamed", port_id);
900 return;
903 /* find the virtual graph that owns the app that owns the client that owns the renamed port */
904 vgraph = ladish_port_get_vgraph(port);
906 if (!ladish_graph_rename_port(virtualizer_ptr->jack_graph, port, new_port_name))
908 log_error("renaming of port in jack graph failed");
911 if (!ladish_graph_rename_port(vgraph, port, new_port_name))
913 log_error("renaming of port in virtual graph failed");
917 static bool ports_connect_request(void * context, ladish_graph_handle graph_handle, ladish_port_handle port1, ladish_port_handle port2)
919 uint64_t port1_id;
920 uint64_t port2_id;
922 ASSERT(ladish_graph_get_opath(graph_handle)); /* studio or room virtual graph */
923 log_info("virtualizer: ports connect request");
925 if (graph_handle == g_studio.studio_graph)
927 port1_id = ladish_port_get_jack_id(port1);
928 port2_id = ladish_port_get_jack_id(port2);
930 else
932 port1_id = ladish_port_get_jack_id_room(port1);
933 port2_id = ladish_port_get_jack_id_room(port2);
936 return graph_proxy_connect_ports(virtualizer_ptr->jack_graph_proxy, port1_id, port2_id);
939 static bool ports_disconnect_request(void * context, ladish_graph_handle graph_handle, uint64_t connection_id)
941 ladish_port_handle port1;
942 ladish_port_handle port2;
943 uint64_t port1_id;
944 uint64_t port2_id;
946 ASSERT(ladish_graph_get_opath(graph_handle)); /* studio or room virtual graph */
947 log_info("virtualizer: ports disconnect request");
949 if (!ladish_graph_get_connection_ports(graph_handle, connection_id, &port1, &port2))
951 log_error("cannot find ports that are disconnect-requested");
952 ASSERT_NO_PASS;
953 return false;
956 if (graph_handle == g_studio.studio_graph)
958 port1_id = ladish_port_get_jack_id(port1);
959 port2_id = ladish_port_get_jack_id(port2);
961 else
963 port1_id = ladish_port_get_jack_id_room(port1);
964 port2_id = ladish_port_get_jack_id_room(port2);
967 return graph_proxy_disconnect_ports(virtualizer_ptr->jack_graph_proxy, port1_id, port2_id);
970 static void ports_connected(void * context, uint64_t client1_id, uint64_t port1_id, uint64_t client2_id, uint64_t port2_id)
972 ladish_port_handle port1;
973 ladish_port_handle port2;
974 uint64_t connection_id;
975 ladish_graph_handle vgraph1;
976 ladish_graph_handle vgraph2;
978 log_info("ports_connected %"PRIu64":%"PRIu64" %"PRIu64":%"PRIu64"", client1_id, port1_id, client2_id, port2_id);
980 if (!lookup_port(virtualizer_ptr, port1_id, &port1, &vgraph1))
982 return;
985 if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2))
987 return;
990 if (vgraph1 != vgraph2)
992 /* TODO */
993 log_error("ignoring connection with endpoints in different vgraphs");
994 return;
997 ladish_graph_add_connection(virtualizer_ptr->jack_graph, port1, port2, false);
999 if (ladish_graph_find_connection(vgraph1, port1, port2, &connection_id))
1001 log_info("showing hidden virtual connection");
1002 ladish_graph_show_connection(vgraph1, connection_id);
1004 else
1006 log_info("creating new virtual connection");
1007 ladish_graph_add_connection(vgraph1, port1, port2, false);
1011 static void ports_disconnected(void * context, uint64_t client1_id, uint64_t port1_id, uint64_t client2_id, uint64_t port2_id)
1013 ladish_port_handle port1;
1014 ladish_port_handle port2;
1015 uint64_t connection_id;
1016 ladish_graph_handle vgraph1;
1017 ladish_graph_handle vgraph2;
1019 log_info("ports_disconnected %"PRIu64":%"PRIu64" %"PRIu64":%"PRIu64"", client1_id, port1_id, client2_id, port2_id);
1021 if (!lookup_port(virtualizer_ptr, port1_id, &port1, &vgraph1))
1023 return;
1026 if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2))
1028 return;
1031 if (vgraph1 != vgraph2)
1033 /* TODO */
1034 log_error("ignoring connection with endpoints in different vgraphs");
1035 return;
1038 if (ladish_graph_find_connection(virtualizer_ptr->jack_graph, port1, port2, &connection_id))
1040 ladish_graph_remove_connection(virtualizer_ptr->jack_graph, connection_id, true);
1042 else
1044 log_error("ports %"PRIu64":%"PRIu64" and %"PRIu64":%"PRIu64" are not connected in the JACK graph", client1_id, port1_id, client2_id, port2_id);
1047 if (ladish_graph_find_connection(vgraph1, port1, port2, &connection_id))
1049 ladish_graph_remove_connection(vgraph1, connection_id, false);
1051 else
1053 log_error("ports %"PRIu64":%"PRIu64" and %"PRIu64":%"PRIu64" are not connected in the virtual graph", client1_id, port1_id, client2_id, port2_id);
1057 #undef virtualizer_ptr
1059 bool
1060 ladish_virtualizer_create(
1061 graph_proxy_handle jack_graph_proxy,
1062 ladish_graph_handle jack_graph,
1063 ladish_virtualizer_handle * handle_ptr)
1065 struct virtualizer * virtualizer_ptr;
1067 virtualizer_ptr = malloc(sizeof(struct virtualizer));
1068 if (virtualizer_ptr == NULL)
1070 log_error("malloc() failed for struct virtualizer");
1071 return false;
1074 virtualizer_ptr->jack_graph_proxy = jack_graph_proxy;
1075 virtualizer_ptr->jack_graph = jack_graph;
1076 virtualizer_ptr->system_client_id = 0;
1077 virtualizer_ptr->our_clients_count = 0;
1079 if (!graph_proxy_attach(
1080 jack_graph_proxy,
1081 virtualizer_ptr,
1082 clear,
1083 client_appeared,
1084 NULL, /* jackdbus does not have client rename functionality (yet) */
1085 client_disappeared,
1086 port_appeared,
1087 port_renamed,
1088 port_disappeared,
1089 ports_connected,
1090 ports_disconnected))
1092 free(virtualizer_ptr);
1093 return false;
1096 *handle_ptr = (ladish_virtualizer_handle)virtualizer_ptr;
1097 return true;
1100 #define virtualizer_ptr ((struct virtualizer *)handle)
1102 void
1103 ladish_virtualizer_set_graph_connection_handlers(
1104 ladish_virtualizer_handle handle,
1105 ladish_graph_handle graph)
1107 ladish_graph_set_connection_handlers(graph, virtualizer_ptr, ports_connect_request, ports_disconnect_request);
1110 unsigned int
1111 ladish_virtualizer_get_our_clients_count(
1112 ladish_virtualizer_handle handle)
1114 return virtualizer_ptr->our_clients_count;
1117 static bool app_has_a2j_ports(ladish_graph_handle jack_graph, const uuid_t app_uuid)
1119 ladish_client_handle a2jclient;
1121 a2jclient = ladish_graph_find_client_by_uuid(jack_graph, g_a2j_uuid);
1122 if (a2jclient == NULL)
1124 return false;
1127 return ladish_graph_client_has_visible_app_port(jack_graph, a2jclient, app_uuid);
1130 bool
1131 ladish_virtualizer_is_hidden_app(
1132 ladish_graph_handle jack_graph,
1133 const uuid_t app_uuid,
1134 const char * app_name)
1136 ladish_client_handle jclient;
1137 ladish_graph_handle vgraph;
1138 uuid_t vclient_uuid;
1139 ladish_client_handle vclient;
1141 //ladish_graph_dump(g_studio.jack_graph);
1143 if (app_has_a2j_ports(jack_graph, app_uuid))
1145 log_info("app '%s' still has a2j ports", app_name);
1146 return false;
1149 jclient = ladish_graph_find_client_by_app(jack_graph, app_uuid);
1150 if (jclient == NULL)
1152 log_info("App without JACK client is treated as hidden one");
1153 return true;
1156 ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */
1158 vgraph = ladish_client_get_vgraph(jclient);
1159 if (vgraph == NULL)
1161 ASSERT_NO_PASS;
1162 return true;
1165 //ladish_graph_dump(vgraph);
1167 if (!ladish_graph_client_looks_empty(jack_graph, jclient) ||
1168 !ladish_graph_client_is_hidden(jack_graph, jclient))
1170 return false;
1173 if (!ladish_client_get_interlink(jclient, vclient_uuid))
1175 if (ladish_graph_client_is_empty(jack_graph, jclient))
1177 log_info("jack client of app '%s' has no interlinked vgraph client and no ports", app_name);
1179 else
1181 log_error("jack client of app '%s' has no interlinked vgraph client", app_name);
1182 ASSERT_NO_PASS;
1184 return true;
1187 vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid);
1188 if (vclient == NULL)
1190 ASSERT_NO_PASS;
1191 return true;
1194 if (!ladish_graph_client_looks_empty(vgraph, vclient))
1196 return false;
1199 ASSERT(ladish_graph_client_is_hidden(vgraph, vclient)); /* vclients are automatically hidden when they start looking empty (on port disappear) */
1200 return true;
1203 struct app_remove_context
1205 uuid_t app_uuid;
1206 const char * app_name;
1209 #define app_info_ptr ((struct app_remove_context *)context)
1211 static
1212 bool
1213 remove_app_port(
1214 void * context,
1215 ladish_graph_handle graph_handle,
1216 void * client_iteration_context_ptr,
1217 ladish_client_handle client_handle,
1218 const char * client_name,
1219 ladish_port_handle port_handle,
1220 const char * port_name,
1221 uint32_t port_type,
1222 uint32_t port_flags)
1224 ladish_graph_handle vgraph;
1225 ladish_client_handle vclient;
1227 if (!ladish_port_belongs_to_app(port_handle, app_info_ptr->app_uuid))
1229 return true;
1232 //log_info("removing port '%s':'%s' (JACK) of app '%s'", client_name, port_name, app_info_ptr->app_name);
1234 vgraph = ladish_port_get_vgraph(port_handle);
1235 if (vgraph == NULL)
1237 log_error("port '%s':'%s' of app '5s' has no vgraph", client_name, port_name, app_info_ptr->app_name);
1238 ASSERT_NO_PASS;
1239 return true;
1242 vclient = ladish_graph_get_port_client(vgraph, port_handle);
1243 if (vgraph == NULL)
1245 log_error("app port '%s':'%s' not found in vgraph '%s'", client_name, port_name, ladish_graph_get_description(vgraph));
1246 ASSERT_NO_PASS;
1247 return true;
1250 log_info(
1251 "removing %s %s port %p of app '%s' ('%s':'%s' in %s)",
1252 port_type == JACKDBUS_PORT_TYPE_AUDIO ? "audio" : "midi",
1253 JACKDBUS_PORT_IS_INPUT(port_flags) ? "input" : "output",
1254 port_handle,
1255 app_info_ptr->app_name,
1256 ladish_graph_get_client_name(vgraph, vclient),
1257 ladish_graph_get_port_name(vgraph, port_handle),
1258 ladish_graph_get_description(vgraph));
1260 ladish_graph_remove_port(graph_handle, port_handle);
1261 ladish_graph_remove_port(vgraph, port_handle);
1263 return true;
1266 #undef app_info_ptr
1268 void
1269 ladish_virtualizer_remove_app(
1270 ladish_graph_handle jack_graph,
1271 const uuid_t app_uuid,
1272 const char * app_name)
1274 ladish_client_handle jclient;
1275 ladish_graph_handle vgraph;
1276 uuid_t vclient_uuid;
1277 ladish_client_handle vclient;
1278 bool is_empty;
1279 struct app_remove_context ctx;
1281 //ladish_graph_dump(g_studio.jack_graph);
1283 uuid_copy(ctx.app_uuid, app_uuid);
1284 ctx.app_name = app_name;
1286 ladish_graph_iterate_nodes(jack_graph, false, &ctx, NULL, remove_app_port, NULL);
1288 jclient = ladish_graph_find_client_by_app(jack_graph, app_uuid);
1289 if (jclient == NULL)
1291 log_info("removing app without JACK client");
1292 return;
1295 ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */
1297 vgraph = ladish_client_get_vgraph(jclient);
1298 if (vgraph == NULL)
1300 ASSERT_NO_PASS;
1301 return;
1304 //ladish_graph_dump(vgraph);
1306 /* check whether the client is empty because this cannot
1307 be checked later because the client was removed
1308 (see where is_empty is used) */
1309 is_empty = ladish_graph_client_is_empty(jack_graph, jclient);
1311 ladish_graph_remove_client(jack_graph, jclient);
1313 if (!ladish_client_get_interlink(jclient, vclient_uuid))
1315 if (is_empty)
1317 /* jack client without ports and thus without vgraph client */
1318 return;
1321 log_error("jack client of app '%s' has no interlinked vgraph client", app_name);
1322 ladish_graph_dump(g_studio.jack_graph);
1323 ladish_graph_dump(vgraph);
1324 ASSERT_NO_PASS;
1325 return;
1328 vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid);
1329 if (vclient == NULL)
1331 ASSERT_NO_PASS;
1332 return;
1335 ladish_graph_remove_client(vgraph, vclient);
1336 ladish_graph_dump(g_studio.jack_graph);
1337 ladish_graph_dump(vgraph);
1340 void
1341 ladish_virtualizer_destroy(
1342 ladish_virtualizer_handle handle)
1344 log_info("ladish_virtualizer_destroy() called");
1346 graph_proxy_detach((graph_proxy_handle)handle, virtualizer_ptr);
1347 free(virtualizer_ptr);
1350 #undef virtualizer_ptr
1352 #define vgraph ((ladish_graph_handle)vgraph_context)
1353 void
1354 ladish_virtualizer_rename_app(
1355 void * vgraph_context,
1356 const uuid_t uuid,
1357 const char * old_name,
1358 const char * new_app_name)
1360 ladish_client_handle client;
1362 client = ladish_graph_find_client_by_app(vgraph, uuid);
1363 if (client != NULL)
1365 ladish_graph_rename_client(vgraph, client, new_app_name);
1368 client = ladish_graph_find_client_by_app(g_studio.jack_graph, uuid);
1369 if (client != NULL)
1371 ladish_graph_rename_client(g_studio.jack_graph, client, new_app_name);
1374 #undef vgraph
1376 bool
1377 ladish_virtualizer_is_system_client(
1378 uuid_t uuid)
1380 if (uuid_compare(uuid, g_system_capture_uuid) == 0)
1382 return true;
1385 if (uuid_compare(uuid, g_system_playback_uuid) == 0)
1387 return true;
1390 return false;
1393 bool ladish_virtualizer_is_a2j_client(ladish_client_handle jclient)
1395 uuid_t jclient_uuid;
1397 ladish_client_get_uuid(jclient, jclient_uuid);
1398 return uuid_compare(jclient_uuid, g_a2j_uuid) == 0;