Merge branch 'stable' into 'main'
[ladish.git] / daemon / virtualizer.c
blob36750cbd7b20670880ac6faf3a4c952c096803f2
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
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"
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 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
59 pid_t pid;
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)
68 pid_t pid;
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);
80 if (app != NULL)
81 break;
83 pid = (pid_t)procfs_get_process_parent((unsigned long long)pid);
84 #if 0
85 if (pid != 0)
87 log_info("parent pid %llu", (unsigned long long)pid);
89 #endif
91 while (pid != 0);
93 if (app == NULL)
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;
110 context.pid = pid;
111 context.app = NULL;
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);
119 return NULL;
122 ASSERT(context.graph != NULL);
123 if (graph_ptr != NULL)
125 *graph_ptr = context.graph;
128 return context.app;
131 struct find_link_port_context
133 uuid_t uuid;
134 uint64_t jack_id;
135 ladish_port_handle port;
136 ladish_graph_handle graph;
139 #define find_link_port_context_ptr ((struct find_link_port_context *)context)
141 static
142 bool
143 find_link_port_vgraph_callback_by_uuid(
144 void * context,
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);
151 if (port != NULL)
153 find_link_port_context_ptr->port = port;
154 find_link_port_context_ptr->graph = graph;
155 return false;
158 return true; /* continue vgraph iteration */
161 static
162 bool
163 find_link_port_vgraph_callback_by_jack_id(
164 void * context,
165 ladish_graph_handle graph,
166 ladish_app_supervisor_handle UNUSED(app_supervisor))
168 ladish_port_handle port;
169 bool room;
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);
176 if (port != NULL)
178 find_link_port_context_ptr->port = port;
179 find_link_port_context_ptr->graph = graph;
180 return false;
183 return true; /* continue vgraph iteration */
186 #undef find_link_port_context_ptr
188 static
189 ladish_graph_handle
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;
199 context.port = 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;
211 static
212 ladish_graph_handle
213 find_link_port_vgraph_by_jack_id(
214 struct virtualizer * UNUSED(virtualizer_ptr),
215 uint64_t jack_id,
216 ladish_port_handle * port_ptr)
218 struct find_link_port_context context;
220 context.jack_id = jack_id;
221 context.graph = NULL;
222 context.port = 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;
234 static
235 bool
236 lookup_port(
237 struct virtualizer * virtualizer_ptr,
238 uint64_t port_id,
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);
246 if (port == NULL)
248 log_error("Unknown JACK port with id %"PRIu64" (dis)connected", port_id);
249 return false;
252 vgraph = ladish_port_get_vgraph(port);
253 if (vgraph == NULL)
255 vgraph = find_link_port_vgraph_by_jack_id(virtualizer_ptr, port_id, NULL);
256 if (vgraph == NULL)
258 log_error("Cannot find vgraph for (dis)connected jmcore port");
259 return false;
262 log_info("link port found in graph %s", ladish_graph_get_description(vgraph));
265 *port_ptr = port;
266 *vgraph_ptr = vgraph;
267 return true;
270 #define virtualizer_ptr ((struct virtualizer *)context)
272 static void clear(void * UNUSED(context))
274 log_info("clear");
277 static void client_appeared(void * context, uint64_t id, const char * jack_name)
279 ladish_client_handle client;
280 const char * a2j_name;
281 bool is_a2j;
282 ladish_app_handle app;
283 uuid_t app_uuid;
284 const char * name;
285 pid_t pid;
286 ladish_graph_handle graph;
287 bool jmcore;
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;
294 name = jack_name;
295 app = NULL;
296 graph = NULL;
297 jmcore = false;
299 if (!graph_proxy_get_client_pid(virtualizer_ptr->jack_graph_proxy, id, &pid))
301 log_info("client %"PRIu64" pid is unknown", id);
303 else
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();
310 if (jmcore)
312 log_info("jmcore client appeared");
314 else
316 app = ladish_find_app_by_pid(pid, &graph);
317 if (app != NULL)
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);
328 if (!jmcore)
330 if (is_a2j)
332 client = ladish_graph_find_client_by_uuid(virtualizer_ptr->jack_graph, g_a2j_uuid);
334 else
336 if (app != NULL)
338 client = ladish_graph_find_client_by_app(virtualizer_ptr->jack_graph, app_uuid);
339 if (client == NULL)
341 log_info("Lookup by app uuid failed, attempting lookup by name '%s'", name);
342 goto find_by_name;
345 else
347 find_by_name:
348 client = ladish_graph_find_client_by_name(virtualizer_ptr->jack_graph, name, true);
352 if (client != NULL)
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);
358 goto exit;
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);
365 goto done;
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);
372 goto exit;
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);
382 goto exit;
385 done:
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;
396 if (app != NULL)
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);
403 ASSERT(graph);
404 ladish_client_set_vgraph(client, graph);
405 virtualizer_ptr->our_clients_count++;
407 else if (jmcore)
409 ladish_client_set_pid(client, pid);
410 ASSERT(ladish_client_get_vgraph(client) == NULL);
412 else
414 /* unknown and internal clients appear in the studio graph */
415 ladish_client_set_vgraph(client, g_studio.studio_graph);
418 exit:
419 return;
422 static void port_disappeared(void * context, uint64_t client_id, uint64_t port_id);
424 bool
425 force_port_disappear(
426 void * context,
427 ladish_graph_handle UNUSED(graph_handle),
428 bool hidden,
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))
436 uint64_t client_id;
437 uint64_t port_id;
439 if (hidden)
441 return true;
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);
450 return true;
453 static void client_disappeared(void * context, uint64_t id)
455 ladish_client_handle client;
456 pid_t pid;
457 uuid_t app_uuid;
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);
464 if (client == NULL)
466 log_error("Unknown JACK client with id %"PRIu64" disappeared", id);
467 return;
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);
482 if (app != NULL)
484 ladish_app_del_pid(app, pid);
486 else
488 log_error("app of disappearing client %"PRIu64" not found. pid is %"PRIu64, id, (uint64_t)pid);
489 ASSERT_NO_PASS;
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);
508 else
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) */
517 static
518 void
519 port_appeared(
520 void * context,
521 uint64_t client_id,
522 uint64_t port_id,
523 const char * real_jack_port_name,
524 bool is_input,
525 bool is_terminal,
526 bool is_midi)
528 ladish_client_handle jack_client;
529 ladish_client_handle vclient;
530 ladish_port_handle port;
531 uint32_t type;
532 uint32_t flags;
533 const char * jack_client_name;
534 const char * vclient_name;
535 bool is_a2j;
536 uuid_t vclient_uuid;
537 pid_t pid;
538 ladish_app_handle app;
539 bool has_app;
540 uuid_t app_uuid;
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;
557 if (is_terminal)
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);
569 goto exit;
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);
577 if (vgraph == NULL)
579 vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, real_jack_port_name, &port);
580 if (vgraph == NULL)
582 log_error("Cannot find vgraph for appeared jmcore port '%s'", real_jack_port_name);
583 goto exit;
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.");
593 goto exit;
596 if (vgraph == g_studio.studio_graph)
598 ladish_port_set_jack_id(port, port_id);
600 else
602 ladish_port_set_jack_id_room(port, port_id);
605 vclient = ladish_graph_get_port_client(vgraph, port);
606 if (vclient == NULL)
608 log_error("link port client not found in vgraph %s", ladish_graph_get_description(vgraph));
609 ASSERT_NO_PASS;
610 goto exit;
613 ladish_graph_show_port(vgraph, port);
614 goto exit;
616 else
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);
624 if (is_a2j)
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))
629 is_a2j = false;
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");
634 goto exit;
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);
642 goto exit;
645 vclient_name = alsa_client_name;
647 else
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);
656 if (app != NULL)
658 ladish_app_get_uuid(app, app_uuid);
659 ASSERT(!uuid_is_null(app_uuid));
660 vclient_name = ladish_app_get_name(app);
661 has_app = true;
662 log_info("ALSA app name is '%s'", vclient_name);
663 ladish_app_add_pid(app, pid);
666 else
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;
681 else
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);
695 if (port != NULL)
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);
710 if (vclient == NULL)
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);
715 ASSERT_NO_PASS;
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 */
722 if (has_app)
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);
752 if (has_app)
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 */
770 if (is_a2j)
772 vclient = ladish_graph_find_client_by_name(vgraph, vclient_name, false);
773 if (vclient == NULL)
775 if (!ladish_client_create(NULL, &vclient))
777 log_error("ladish_client_create() failed.");
778 goto free_alsa_names;
781 if (has_app)
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");
799 if (!is_input)
800 { /* output capture port */
802 vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_capture_uuid);
803 if (vclient == NULL)
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;
819 else
820 { /* input playback port */
821 vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_playback_uuid);
822 if (vclient == NULL)
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;
838 else
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);
847 else
849 if (has_app)
851 vclient = ladish_graph_find_client_by_app(vgraph, app_uuid);
852 if (vclient == NULL)
854 log_info("Lookup by app uuid failed, attempting lookup by name '%s'", vclient_name);
855 goto find_by_name;
858 else
860 find_by_name:
861 vclient = ladish_graph_find_client_by_name(vgraph, vclient_name, true);
864 if (vclient == NULL)
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);
875 if (has_app)
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;
887 else
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 */
899 if (is_a2j)
901 vport_name = alsa_port_name;
903 else
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;
914 free_alsa_names:
915 free(a2j_fake_jack_port_name);
916 free(alsa_client_name);
917 free(alsa_port_name);
919 exit:
920 return;
923 static void maybe_clear_a2j_port_pid(ladish_graph_handle vgraph, ladish_client_handle jclient, ladish_port_handle port)
925 const char * opath;
926 uuid_t app_uuid;
927 ladish_app_handle app;
928 ladish_app_supervisor_handle app_supervisor;
929 pid_t pid;
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;
951 bool jmcore;
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);
956 if (jclient == NULL)
958 log_error("Port of unknown JACK client with id %"PRIu64" disappeared", client_id);
959 return;
962 port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
963 if (port == NULL)
965 log_error("Unknown JACK port with id %"PRIu64" disappeared", port_id);
966 return;
969 /* find the virtual graph that owns the app that owns the client that owns the disappeared port */
970 jmcore = false;
971 vgraph = ladish_port_get_vgraph(port);
972 if (vgraph == NULL)
974 vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, ladish_graph_get_port_name(virtualizer_ptr->jack_graph, port), NULL);
975 if (vgraph == NULL)
977 log_error("Cannot find vgraph for disappeared jmcore port");
978 ASSERT_NO_PASS;
979 return;
982 jmcore = true;
983 ladish_graph_remove_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true);
985 else
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 */
994 if (!jmcore)
996 ladish_port_set_jack_id(port, 0);
997 ladish_graph_hide_port(virtualizer_ptr->jack_graph, port);
999 if (vgraph != NULL)
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);
1009 else
1011 if (!jmcore)
1013 ladish_graph_remove_port(virtualizer_ptr->jack_graph, port);
1016 if (vgraph != NULL)
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);
1031 static
1032 void
1033 port_renamed(
1034 void * context,
1035 uint64_t client_id,
1036 uint64_t port_id,
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);
1046 if (port == NULL)
1048 log_error("Unknown JACK port with id %"PRIu64" was renamed", port_id);
1049 return;
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)
1068 uint64_t port1_id;
1069 uint64_t port2_id;
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);
1079 else
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;
1092 uint64_t port1_id;
1093 uint64_t port2_id;
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");
1101 ASSERT_NO_PASS;
1102 return false;
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);
1110 else
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))
1131 return;
1134 if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2))
1136 return;
1139 if (vgraph1 != vgraph2)
1141 /* TODO */
1142 log_error("ignoring connection with endpoints in different vgraphs");
1143 return;
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);
1153 else
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))
1172 return;
1175 if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2))
1177 return;
1180 if (vgraph1 != vgraph2)
1182 /* TODO */
1183 log_error("ignoring connection with endpoints in different vgraphs");
1184 return;
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);
1191 else
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);
1200 else
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
1208 bool
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");
1220 return false;
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(
1230 jack_graph_proxy,
1231 virtualizer_ptr,
1232 clear,
1233 client_appeared,
1234 NULL, /* jackdbus does not have client rename functionality (yet) */
1235 client_disappeared,
1236 port_appeared,
1237 port_renamed,
1238 port_disappeared,
1239 ports_connected,
1240 ports_disconnected))
1242 free(virtualizer_ptr);
1243 return false;
1246 *handle_ptr = (ladish_virtualizer_handle)virtualizer_ptr;
1247 return true;
1250 #define virtualizer_ptr ((struct virtualizer *)handle)
1252 void
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);
1260 unsigned int
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)
1274 return false;
1277 return ladish_graph_client_has_visible_app_port(jack_graph, a2jclient, app_uuid);
1280 bool
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);
1296 return false;
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");
1303 return true;
1306 ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */
1308 vgraph = ladish_client_get_vgraph(jclient);
1309 if (vgraph == NULL)
1311 ASSERT_NO_PASS;
1312 return true;
1315 //ladish_graph_dump(vgraph);
1317 if (!ladish_graph_client_looks_empty(jack_graph, jclient) ||
1318 !ladish_graph_client_is_hidden(jack_graph, jclient))
1320 return false;
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);
1329 else
1331 log_error("jack client of app '%s' has no interlinked vgraph client", app_name);
1332 ASSERT_NO_PASS;
1334 return true;
1337 vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid);
1338 if (vclient == NULL)
1340 ASSERT_NO_PASS;
1341 return true;
1344 if (!ladish_graph_client_looks_empty(vgraph, vclient))
1346 return false;
1349 ASSERT(ladish_graph_client_is_hidden(vgraph, vclient)); /* vclients are automatically hidden when they start looking empty (on port disappear) */
1350 return true;
1353 struct app_remove_context
1355 uuid_t app_uuid;
1356 const char * app_name;
1359 #define app_info_ptr ((struct app_remove_context *)context)
1361 static
1362 bool
1363 remove_app_port(
1364 void * 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,
1372 uint32_t port_type,
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))
1380 return true;
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);
1386 if (vgraph == NULL)
1388 log_error("port '%s':'%s' of app '%s' has no vgraph", client_name, port_name, app_info_ptr->app_name);
1389 ASSERT_NO_PASS;
1390 return true;
1393 vclient = ladish_graph_get_port_client(vgraph, port_handle);
1394 if (vgraph == NULL)
1396 log_error("app port '%s':'%s' not found in vgraph '%s'", client_name, port_name, ladish_graph_get_description(vgraph));
1397 ASSERT_NO_PASS;
1398 return true;
1401 log_info(
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",
1405 port_handle,
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);
1414 return true;
1417 #undef app_info_ptr
1419 void
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;
1429 bool is_empty;
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");
1443 return;
1446 ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */
1448 vgraph = ladish_client_get_vgraph(jclient);
1449 if (vgraph == NULL)
1451 ASSERT_NO_PASS;
1452 return;
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))
1466 if (is_empty)
1468 /* jack client without ports and thus without vgraph client */
1469 return;
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);
1475 ASSERT_NO_PASS;
1476 return;
1479 vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid);
1480 if (vclient == NULL)
1482 ASSERT_NO_PASS;
1483 return;
1486 ladish_graph_remove_client(vgraph, vclient);
1487 ladish_graph_dump(g_studio.jack_graph);
1488 ladish_graph_dump(vgraph);
1491 void
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)
1504 void
1505 ladish_virtualizer_rename_app(
1506 void * vgraph_context,
1507 const uuid_t uuid,
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);
1514 if (client != NULL)
1516 ladish_graph_rename_client(vgraph, client, new_app_name);
1519 client = ladish_graph_find_client_by_app(g_studio.jack_graph, uuid);
1520 if (client != NULL)
1522 ladish_graph_rename_client(g_studio.jack_graph, client, new_app_name);
1525 #undef vgraph
1527 bool
1528 ladish_virtualizer_is_system_client(
1529 uuid_t uuid)
1531 if (uuid_compare(uuid, g_system_capture_uuid) == 0)
1533 return true;
1536 if (uuid_compare(uuid, g_system_playback_uuid) == 0)
1538 return true;
1541 return false;
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;
1552 static
1553 bool
1554 move_capture_port_callback(
1555 void * context,
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);
1572 return true;
1575 bool ladish_virtualizer_split_client(ladish_graph_handle vgraph, uint64_t client_id)
1577 ladish_client_handle vclient1;
1578 ladish_client_handle vclient2;
1579 const char * name;
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));
1585 return false;
1588 name = ladish_graph_get_client_name(vgraph, vclient1);
1590 if (!ladish_client_create(NULL, &vclient2))
1592 log_error("ladish_client_create() failed.");
1593 return false;
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);
1603 return false;
1606 return ladish_graph_interate_client_ports(vgraph, vclient1, vclient2, move_capture_port_callback);
1609 static
1610 bool
1611 move_port_callback(
1612 void * context,
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);
1624 return true;
1627 bool
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");
1639 return false;
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));
1646 return false;
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));
1653 return false;
1656 ladish_graph_interate_client_ports(vgraph, vclient2, vclient1, move_port_callback);
1658 ladish_graph_remove_client(vgraph, vclient2);
1660 return true;