/* -*- Mode: C ; c-basic-offset: 2 -*- */ /* * LADI Session Handler (ladish) * * Copyright (C) 2009,2010,2011,2012 Nedko Arnaudov * ************************************************************************** * This file contains implementation of the graph virtualizer object ************************************************************************** * * LADI Session Handler is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * LADI Session Handler is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LADI Session Handler. If not, see * or write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "virtualizer.h" #include "../dbus_constants.h" #include "../proxies/a2j_proxy.h" #include "../proxies/jmcore_proxy.h" #include "procfs.h" #include "app_supervisor.h" #include "studio_internal.h" #include "../common/catdup.h" #include "room.h" #include "studio.h" #include "../alsapid/alsapid.h" struct virtualizer { graph_proxy_handle jack_graph_proxy; ladish_graph_handle jack_graph; uint64_t system_client_id; unsigned int our_clients_count; }; /* 47c1cd18-7b21-4389-bec4-6e0658e1d6b1 */ UUID_DEFINE(g_system_capture_uuid,0x47,0xC1,0xCD,0x18,0x7B,0x21,0x43,0x89,0xBE,0xC4,0x6E,0x06,0x58,0xE1,0xD6,0xB1); /* b2a0bb06-28d8-4bfe-956e-eb24378f9629 */ UUID_DEFINE(g_system_playback_uuid,0xB2,0xA0,0xBB,0x06,0x28,0xD8,0x4B,0xFE,0x95,0x6E,0xEB,0x24,0x37,0x8F,0x96,0x29); /* be23a242-e2b2-11de-b795-002618af5e42 */ UUID_DEFINE(g_a2j_uuid,0xBE,0x23,0xA2,0x42,0xE2,0xB2,0x11,0xDE,0xB7,0x95,0x00,0x26,0x18,0xAF,0x5E,0x42); struct app_find_context { pid_t pid; ladish_graph_handle graph; ladish_app_handle app; }; #define app_find_context_ptr ((struct app_find_context *)context) static bool lookup_app_in_supervisor(void * context, ladish_graph_handle graph, ladish_app_supervisor_handle app_supervisor) { pid_t pid; ladish_app_handle app; /* we stop iteration when app is found */ ASSERT(app_find_context_ptr->app == NULL && app_find_context_ptr->graph == NULL); //log_info("checking app supervisor \"%s\" for pid %llu", ladish_app_supervisor_get_name(app_supervisor), (unsigned long long)pid); pid = app_find_context_ptr->pid; do { app = ladish_app_supervisor_find_app_by_pid(app_supervisor, pid); if (app != NULL) break; pid = (pid_t)procfs_get_process_parent((unsigned long long)pid); #if 0 if (pid != 0) { log_info("parent pid %llu", (unsigned long long)pid); } #endif } while (pid != 0); if (app == NULL) { /* app not found in current supervisor */ return true; /* continue app supervisor iteration */ } app_find_context_ptr->app = app; app_find_context_ptr->graph = graph; return false; /* stop app supervisor iteration */ } #undef app_find_context_ptr ladish_app_handle ladish_find_app_by_pid(pid_t pid, ladish_graph_handle * graph_ptr) { struct app_find_context context; context.pid = pid; context.app = NULL; context.graph = NULL; ladish_studio_iterate_virtual_graphs(&context, lookup_app_in_supervisor); if (context.app == NULL) { /* app not found */ ASSERT(context.graph == NULL); return NULL; } ASSERT(context.graph != NULL); if (graph_ptr != NULL) { *graph_ptr = context.graph; } return context.app; } struct find_link_port_context { uuid_t uuid; uint64_t jack_id; ladish_port_handle port; ladish_graph_handle graph; }; #define find_link_port_context_ptr ((struct find_link_port_context *)context) static bool find_link_port_vgraph_callback_by_uuid( void * context, ladish_graph_handle graph, ladish_app_supervisor_handle UNUSED(app_supervisor)) { ladish_port_handle port; port = ladish_graph_find_port_by_uuid(graph, find_link_port_context_ptr->uuid, true, NULL); if (port != NULL) { find_link_port_context_ptr->port = port; find_link_port_context_ptr->graph = graph; return false; } return true; /* continue vgraph iteration */ } static bool find_link_port_vgraph_callback_by_jack_id( void * context, ladish_graph_handle graph, ladish_app_supervisor_handle UNUSED(app_supervisor)) { ladish_port_handle port; bool room; log_info("searching link port with jack id %"PRIu64" in graph %s", find_link_port_context_ptr->jack_id, ladish_graph_get_description(graph)); room = graph != g_studio.studio_graph; port = ladish_graph_find_port_by_jack_id(graph, find_link_port_context_ptr->jack_id, room, !room); if (port != NULL) { find_link_port_context_ptr->port = port; find_link_port_context_ptr->graph = graph; return false; } return true; /* continue vgraph iteration */ } #undef find_link_port_context_ptr static ladish_graph_handle find_link_port_vgraph_by_uuid( struct virtualizer * UNUSED(virtualizer_ptr), const char * port_name, ladish_port_handle * port_ptr) { struct find_link_port_context context; uuid_parse(port_name, context.uuid); context.graph = NULL; context.port = NULL; ladish_studio_iterate_virtual_graphs(&context, find_link_port_vgraph_callback_by_uuid); if (port_ptr != NULL && context.graph != NULL) { *port_ptr = context.port; } return context.graph; } static ladish_graph_handle find_link_port_vgraph_by_jack_id( struct virtualizer * UNUSED(virtualizer_ptr), uint64_t jack_id, ladish_port_handle * port_ptr) { struct find_link_port_context context; context.jack_id = jack_id; context.graph = NULL; context.port = NULL; ladish_studio_iterate_virtual_graphs(&context, find_link_port_vgraph_callback_by_jack_id); if (port_ptr != NULL && context.graph != NULL) { *port_ptr = context.port; } return context.graph; } static bool lookup_port( struct virtualizer * virtualizer_ptr, uint64_t port_id, ladish_port_handle * port_ptr, ladish_graph_handle * vgraph_ptr) { ladish_port_handle port; ladish_graph_handle vgraph; port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true); if (port == NULL) { log_error("Unknown JACK port with id %"PRIu64" (dis)connected", port_id); return false; } vgraph = ladish_port_get_vgraph(port); if (vgraph == NULL) { vgraph = find_link_port_vgraph_by_jack_id(virtualizer_ptr, port_id, NULL); if (vgraph == NULL) { log_error("Cannot find vgraph for (dis)connected jmcore port"); return false; } log_info("link port found in graph %s", ladish_graph_get_description(vgraph)); } *port_ptr = port; *vgraph_ptr = vgraph; return true; } #define virtualizer_ptr ((struct virtualizer *)context) static void clear(void * UNUSED(context)) { log_info("clear"); } static void client_appeared(void * context, uint64_t id, const char * jack_name) { ladish_client_handle client; const char * a2j_name; bool is_a2j; ladish_app_handle app; uuid_t app_uuid; const char * name; pid_t pid; ladish_graph_handle graph; bool jmcore; log_info("client_appeared(%"PRIu64", %s)", id, jack_name); a2j_name = a2j_proxy_get_jack_client_name_cached(); is_a2j = a2j_name != NULL && strcmp(a2j_name, jack_name) == 0; name = jack_name; app = NULL; graph = NULL; jmcore = false; if (!graph_proxy_get_client_pid(virtualizer_ptr->jack_graph_proxy, id, &pid)) { log_info("client %"PRIu64" pid is unknown", id); } else { log_info("client pid is %"PRId64, (int64_t)pid); if (pid != 0) /* skip internal clients that will match the pending clients in the graph, both have zero pid */ { jmcore = pid == jmcore_proxy_get_pid_cached(); if (jmcore) { log_info("jmcore client appeared"); } else { app = ladish_find_app_by_pid(pid, &graph); if (app != NULL) { ladish_app_get_uuid(app, app_uuid); ASSERT(!uuid_is_null(app_uuid)); name = ladish_app_get_name(app); log_info("app name is '%s'", name); } } } } if (!jmcore) { if (is_a2j) { client = ladish_graph_find_client_by_uuid(virtualizer_ptr->jack_graph, g_a2j_uuid); } else { if (app != NULL) { client = ladish_graph_find_client_by_app(virtualizer_ptr->jack_graph, app_uuid); if (client == NULL) { log_info("Lookup by app uuid failed, attempting lookup by name '%s'", name); goto find_by_name; } } else { find_by_name: client = ladish_graph_find_client_by_name(virtualizer_ptr->jack_graph, name, true); } } if (client != NULL) { log_info("found existing client"); if (ladish_client_get_jack_id(client) != 0) { log_error("Ignoring client with duplicate name '%s' ('%s')", name, jack_name); goto exit; } ladish_client_set_jack_name(client, jack_name); ladish_client_set_jack_id(client, id); ladish_graph_show_client(virtualizer_ptr->jack_graph, client); goto done; } } if (!ladish_client_create(is_a2j ? g_a2j_uuid : NULL, &client)) { log_error("ladish_client_create() failed. Ignoring client %"PRIu64" (%s)", id, jack_name); goto exit; } ladish_client_set_jack_id(client, id); ladish_client_set_jack_name(client, jack_name); if (!ladish_graph_add_client(virtualizer_ptr->jack_graph, client, name, false)) { log_error("ladish_graph_add_client() failed to add client %"PRIu64" (%s) to JACK graph", id, name); ladish_client_destroy(client); goto exit; } done: if (strcmp(jack_name, "system") == 0) { virtualizer_ptr->system_client_id = id; } if (app != NULL) { /* interlink client and app */ ladish_app_add_pid(app, pid); ladish_client_set_pid(client, pid); ladish_client_set_app(client, app_uuid); ASSERT(graph); ladish_client_set_vgraph(client, graph); virtualizer_ptr->our_clients_count++; } else if (jmcore) { ladish_client_set_pid(client, pid); ASSERT(ladish_client_get_vgraph(client) == NULL); } else { /* unknown and internal clients appear in the studio graph */ ladish_client_set_vgraph(client, g_studio.studio_graph); } exit: return; } static void port_disappeared(void * context, uint64_t client_id, uint64_t port_id); bool force_port_disappear( void * context, ladish_graph_handle UNUSED(graph_handle), bool hidden, ladish_client_handle client_handle, const char * client_name, ladish_port_handle port_handle, const char * port_name, uint32_t UNUSED(port_type), uint32_t UNUSED(port_flags)) { uint64_t client_id; uint64_t port_id; if (hidden) { return true; } log_error("forcing disappear of port '%s':'%s'", client_name, port_name); client_id = ladish_client_get_jack_id(client_handle); port_id = ladish_port_get_jack_id(port_handle); port_disappeared(context, client_id, port_id); return true; } static void client_disappeared(void * context, uint64_t id) { ladish_client_handle client; pid_t pid; uuid_t app_uuid; ladish_app_handle app; ladish_graph_handle vgraph; log_info("client_disappeared(%"PRIu64")", id); client = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, id); if (client == NULL) { log_error("Unknown JACK client with id %"PRIu64" disappeared", id); return; } log_info("client disappeared: '%s'", ladish_graph_get_client_name(virtualizer_ptr->jack_graph, client)); /* This is a workaround for jack2/jackdbus bug. */ ladish_graph_interate_client_ports(virtualizer_ptr->jack_graph, client, context, force_port_disappear); vgraph = ladish_client_get_vgraph(client); pid = ladish_client_get_pid(client); if (ladish_client_get_app(client, app_uuid)) { virtualizer_ptr->our_clients_count--; app = ladish_studio_find_app_by_uuid(app_uuid); if (app != NULL) { ladish_app_del_pid(app, pid); } else { log_error("app of disappearing client %"PRIu64" not found. pid is %"PRIu64, id, (uint64_t)pid); ASSERT_NO_PASS; } } if (id == virtualizer_ptr->system_client_id) { virtualizer_ptr->system_client_id = 0; } if (vgraph != NULL && ladish_graph_is_persist(vgraph)) /* if client is supposed to be persisted */ { ladish_client_set_jack_id(client, 0); ladish_graph_hide_client(virtualizer_ptr->jack_graph, client); } else { ladish_graph_remove_client(virtualizer_ptr->jack_graph, client); ladish_client_destroy(client); /* no need to clear vclient interlink because it either does not exist (vgraph is NULL) or * it will be destroyed before it is accessed (persist flag is cleared on room deletion) */ } } static void port_appeared( void * context, uint64_t client_id, uint64_t port_id, const char * real_jack_port_name, bool is_input, bool is_terminal, bool is_midi) { ladish_client_handle jack_client; ladish_client_handle vclient; ladish_port_handle port; uint32_t type; uint32_t flags; const char * jack_client_name; const char * vclient_name; bool is_a2j; uuid_t vclient_uuid; pid_t pid; ladish_app_handle app; bool has_app; uuid_t app_uuid; char * alsa_client_name; char * alsa_port_name; char * a2j_fake_jack_port_name; uint32_t alsa_client_id; const char * jack_port_name; const char * vport_name; ladish_graph_handle vgraph; 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"); alsa_client_name = NULL; alsa_port_name = NULL; a2j_fake_jack_port_name = NULL; type = is_midi ? JACKDBUS_PORT_TYPE_MIDI : JACKDBUS_PORT_TYPE_AUDIO; flags = is_input ? JACKDBUS_PORT_FLAG_INPUT : JACKDBUS_PORT_FLAG_OUTPUT; if (is_terminal) { flags |= JACKDBUS_PORT_FLAG_TERMINAL; } /********************/ /* gather info about the appeared port */ jack_client = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, client_id); if (jack_client == NULL) { log_error("Port of unknown JACK client with id %"PRIu64" appeared", client_id); goto exit; } pid = ladish_client_get_pid(jack_client); has_app = ladish_client_get_app(jack_client, app_uuid); /* find the virtual graph that owns the app that owns the client that owns the appeared port */ vgraph = ladish_client_get_vgraph(jack_client); if (vgraph == NULL) { vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, real_jack_port_name, &port); if (vgraph == NULL) { log_error("Cannot find vgraph for appeared jmcore port '%s'", real_jack_port_name); goto exit; } /* jmcore port appeared */ log_info("jmcore port appeared in vgraph %s", ladish_graph_get_description(vgraph)); if (!ladish_graph_add_port(virtualizer_ptr->jack_graph, jack_client, port, real_jack_port_name, type, flags, false)) { log_error("ladish_graph_add_port() failed."); goto exit; } if (vgraph == g_studio.studio_graph) { ladish_port_set_jack_id(port, port_id); } else { ladish_port_set_jack_id_room(port, port_id); } vclient = ladish_graph_get_port_client(vgraph, port); if (vclient == NULL) { log_error("link port client not found in vgraph %s", ladish_graph_get_description(vgraph)); ASSERT_NO_PASS; goto exit; } ladish_graph_show_port(vgraph, port); goto exit; } else { //log_info("Port of virtual graph '%s'", ladish_graph_get_description(vgraph)); } jack_client_name = ladish_graph_get_client_name(virtualizer_ptr->jack_graph, jack_client); is_a2j = ladish_virtualizer_is_a2j_client(jack_client); if (is_a2j) { log_info("a2j port appeared"); if (!a2j_proxy_map_jack_port(real_jack_port_name, &alsa_client_name, &alsa_port_name, &alsa_client_id)) { is_a2j = false; alsa_client_name = catdup("FAILED ", jack_client_name); if (alsa_client_name == NULL) { log_error("catdup failed to duplicate a2j jack client name after map failure"); goto exit; } alsa_port_name = strdup(real_jack_port_name); if (alsa_port_name == NULL) { log_error("catdup failed to duplicate a2j jack port name after map failure"); free(alsa_client_name); goto exit; } vclient_name = alsa_client_name; } else { log_info("a2j: '%s':'%s' (%"PRIu32")", alsa_client_name, alsa_port_name, alsa_client_id); vclient_name = alsa_client_name; if (alsapid_get_pid(alsa_client_id, &pid)) { log_info("ALSA client pid is %lld", (long long)pid); app = ladish_find_app_by_pid(pid, &vgraph); if (app != NULL) { ladish_app_get_uuid(app, app_uuid); ASSERT(!uuid_is_null(app_uuid)); vclient_name = ladish_app_get_name(app); has_app = true; log_info("ALSA app name is '%s'", vclient_name); ladish_app_add_pid(app, pid); } } else { log_error("UNKNOWN ALSA client pid"); } } a2j_fake_jack_port_name = catdup4(vclient_name, is_input ? " (playback)" : " (capture)", ": ", alsa_port_name); if (a2j_fake_jack_port_name == NULL) { log_error("catdup4() failed"); goto free_alsa_names; } jack_port_name = a2j_fake_jack_port_name; } else { vclient_name = jack_client_name; jack_port_name = real_jack_port_name; } /********************/ /* search (by name) the appeared port in jack graph * if found - show it in both graphs. * if not found - create new port and add it to the jack graph. * Then process to adding it to virtual graph */ port = ladish_graph_find_port_by_name(virtualizer_ptr->jack_graph, jack_client, jack_port_name, vgraph); if (port != NULL) { log_info("found existing port %p", port); if (ladish_port_get_jack_id(port) != 0) { log_error("Ignoring duplicate JACK port '%s':'%s'", jack_client_name, jack_port_name); goto free_alsa_names; } ladish_port_set_jack_id(port, port_id); ladish_graph_adjust_port(virtualizer_ptr->jack_graph, port, type, flags); ladish_graph_show_port(virtualizer_ptr->jack_graph, port); vclient = ladish_graph_get_port_client(vgraph, port); if (vclient == NULL) { log_error("JACK port not found in virtual graph '%s'", ladish_graph_get_description(vgraph)); ladish_graph_dump(g_studio.jack_graph); ladish_graph_dump(vgraph); ASSERT_NO_PASS; goto free_alsa_names; } /* for normal ports, one can find the app_uuid through the jack client, but for a2j ports the jack client is shared between graphs */ /* clients and ports can be reused if they were started externally then through ladish */ if (has_app) { ladish_port_set_app(port, app_uuid); ladish_port_set_pid(port, pid); if (!ladish_client_has_app(vclient)) { ladish_client_set_app(vclient, app_uuid); } } ladish_dict_set(ladish_port_get_dict(port), URI_A2J_PORT, is_a2j ? "yes" : "no"); ladish_client_set_jack_id(vclient, client_id); ladish_graph_adjust_port(vgraph, port, type, flags); ladish_graph_show_port(vgraph, port); goto free_alsa_names; } if (!ladish_port_create(NULL, false, &port)) { log_error("ladish_port_create() failed."); goto free_alsa_names; } /* set port jack id so invisible connections to/from it can be restored */ ladish_port_set_jack_id(port, port_id); /* for normal ports, one can find the vgraph and app_uuid through the jack client, but for a2j ports the jack client is shared between graphs */ ladish_port_set_vgraph(port, vgraph); if (has_app) { ladish_port_set_app(port, app_uuid); ladish_port_set_pid(port, pid); } ladish_dict_set(ladish_port_get_dict(port), URI_A2J_PORT, is_a2j ? "yes" : "no"); if (!ladish_graph_add_port(virtualizer_ptr->jack_graph, jack_client, port, jack_port_name, type, flags, false)) { log_error("ladish_graph_add_port() failed."); ladish_port_destroy(port); goto free_alsa_names; } /********************/ /* find/create the virtual client where port will be added */ if (is_a2j) { vclient = ladish_graph_find_client_by_name(vgraph, vclient_name, false); if (vclient == NULL) { if (!ladish_client_create(NULL, &vclient)) { log_error("ladish_client_create() failed."); goto free_alsa_names; } if (has_app) { ladish_client_set_app(vclient, app_uuid); } if (!ladish_graph_add_client(vgraph, vclient, vclient_name, false)) { log_error("ladish_graph_add_client() failed."); ladish_client_destroy(vclient); goto free_alsa_names; } } } else if (client_id == virtualizer_ptr->system_client_id) { log_info("system client port appeared"); if (!is_input) { /* output capture port */ vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_capture_uuid); if (vclient == NULL) { if (!ladish_client_create(g_system_capture_uuid, &vclient)) { log_error("ladish_client_create() failed."); goto free_alsa_names; } if (!ladish_graph_add_client(vgraph, vclient, "Hardware Capture", false)) { log_error("ladish_graph_add_client() failed."); ladish_client_destroy(vclient); goto free_alsa_names; } } } else { /* input playback port */ vclient = ladish_graph_find_client_by_uuid(vgraph, g_system_playback_uuid); if (vclient == NULL) { if (!ladish_client_create(g_system_playback_uuid, &vclient)) { log_error("ladish_client_create() failed."); goto free_alsa_names; } if (!ladish_graph_add_client(vgraph, vclient, "Hardware Playback", false)) { ladish_client_destroy(vclient); goto free_alsa_names; } } } } else { /* non-system client */ log_info("non-system client port appeared"); if (ladish_client_get_interlink(jack_client, vclient_uuid)) { vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid); ASSERT(vclient != NULL); } else { if (has_app) { vclient = ladish_graph_find_client_by_app(vgraph, app_uuid); if (vclient == NULL) { log_info("Lookup by app uuid failed, attempting lookup by name '%s'", vclient_name); goto find_by_name; } } else { find_by_name: vclient = ladish_graph_find_client_by_name(vgraph, vclient_name, true); } if (vclient == NULL) { log_info("creating new vclient"); if (!ladish_client_create(NULL, &vclient)) { log_error("ladish_client_create() failed."); goto free_alsa_names; } ladish_client_interlink(vclient, jack_client); if (has_app) { ladish_client_set_app(vclient, app_uuid); } if (!ladish_graph_add_client(vgraph, vclient, vclient_name, false)) { log_error("ladish_graph_add_client() failed to add client '%s' to virtual graph", jack_client_name); ladish_client_destroy(vclient); goto free_alsa_names; } } else { /* vclient exists but is not interlinked with vclient */ /* this can happen when client is created because of a2j port appear */ ladish_client_interlink(vclient, jack_client); } } } /********************/ /* add newly appeared port to the virtual graph */ if (is_a2j) { vport_name = alsa_port_name; } else { vport_name = jack_port_name; } if (!ladish_graph_add_port(vgraph, vclient, port, vport_name, type, flags, false)) { log_error("ladish_graph_add_port() failed."); goto free_alsa_names; } free_alsa_names: free(a2j_fake_jack_port_name); free(alsa_client_name); free(alsa_port_name); exit: return; } static void maybe_clear_a2j_port_pid(ladish_graph_handle vgraph, ladish_client_handle jclient, ladish_port_handle port) { const char * opath; uuid_t app_uuid; ladish_app_handle app; ladish_app_supervisor_handle app_supervisor; pid_t pid; if (ladish_virtualizer_is_a2j_client(jclient) && ladish_port_get_app(port, app_uuid)) { pid = ladish_port_get_pid(port); opath = ladish_graph_get_opath(vgraph); log_info("releasing reference for pid %d of a2j port in %s", (int)pid, ladish_graph_get_description(vgraph)); app_supervisor = ladish_studio_find_app_supervisor(opath); app = ladish_app_supervisor_find_app_by_uuid(app_supervisor, app_uuid); if (app != NULL && pid != 0) { ladish_app_del_pid(app, pid); } } } static void port_disappeared(void * context, uint64_t client_id, uint64_t port_id) { ladish_client_handle jclient; ladish_client_handle vclient; ladish_port_handle port; ladish_graph_handle vgraph; bool jmcore; log_info("port_disappeared(%"PRIu64", %"PRIu64")", client_id, port_id); jclient = ladish_graph_find_client_by_jack_id(virtualizer_ptr->jack_graph, client_id); if (jclient == NULL) { log_error("Port of unknown JACK client with id %"PRIu64" disappeared", client_id); return; } port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true); if (port == NULL) { log_error("Unknown JACK port with id %"PRIu64" disappeared", port_id); return; } /* find the virtual graph that owns the app that owns the client that owns the disappeared port */ jmcore = false; vgraph = ladish_port_get_vgraph(port); if (vgraph == NULL) { vgraph = find_link_port_vgraph_by_uuid(virtualizer_ptr, ladish_graph_get_port_name(virtualizer_ptr->jack_graph, port), NULL); if (vgraph == NULL) { log_error("Cannot find vgraph for disappeared jmcore port"); ASSERT_NO_PASS; return; } jmcore = true; ladish_graph_remove_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true); } else { maybe_clear_a2j_port_pid(vgraph, jclient, port); } ladish_port_set_pid(port, 0); if (ladish_graph_is_persist(vgraph)) /* if port is supposed to be persisted */ { if (!jmcore) { ladish_port_set_jack_id(port, 0); ladish_graph_hide_port(virtualizer_ptr->jack_graph, port); } if (vgraph != NULL) { ladish_graph_hide_port(vgraph, port); vclient = ladish_graph_get_port_client(vgraph, port); if (ladish_graph_client_looks_empty(vgraph, vclient)) { ladish_graph_hide_client(vgraph, vclient); } } } else { if (!jmcore) { ladish_graph_remove_port(virtualizer_ptr->jack_graph, port); } if (vgraph != NULL) { vclient = ladish_graph_remove_port(vgraph, port); if (vclient != NULL) { if (ladish_graph_client_is_empty(vgraph, vclient)) { ladish_graph_remove_client(vgraph, vclient); ladish_client_clear_interlink(jclient); } } } } } static void port_renamed( void * context, uint64_t client_id, uint64_t port_id, const char * old_port_name, const char * new_port_name) { ladish_port_handle port; ladish_graph_handle vgraph; log_info("port_renamed(%"PRIu64":%"PRIu64", '%s', '%s')", client_id, port_id, old_port_name, new_port_name); port = ladish_graph_find_port_by_jack_id(virtualizer_ptr->jack_graph, port_id, true, true); if (port == NULL) { log_error("Unknown JACK port with id %"PRIu64" was renamed", port_id); return; } /* find the virtual graph that owns the app that owns the client that owns the renamed port */ vgraph = ladish_port_get_vgraph(port); if (!ladish_graph_rename_port(virtualizer_ptr->jack_graph, port, new_port_name)) { log_error("renaming of port in jack graph failed"); } if (!ladish_graph_rename_port(vgraph, port, new_port_name)) { log_error("renaming of port in virtual graph failed"); } } static bool ports_connect_request(void * context, ladish_graph_handle graph_handle, ladish_port_handle port1, ladish_port_handle port2) { uint64_t port1_id; uint64_t port2_id; ASSERT(ladish_graph_get_opath(graph_handle)); /* studio or room virtual graph */ log_info("virtualizer: ports connect request"); if (graph_handle == g_studio.studio_graph) { port1_id = ladish_port_get_jack_id(port1); port2_id = ladish_port_get_jack_id(port2); } else { port1_id = ladish_port_get_jack_id_room(port1); port2_id = ladish_port_get_jack_id_room(port2); } return graph_proxy_connect_ports(virtualizer_ptr->jack_graph_proxy, port1_id, port2_id); } static bool ports_disconnect_request(void * context, ladish_graph_handle graph_handle, uint64_t connection_id) { ladish_port_handle port1; ladish_port_handle port2; uint64_t port1_id; uint64_t port2_id; ASSERT(ladish_graph_get_opath(graph_handle)); /* studio or room virtual graph */ log_info("virtualizer: ports disconnect request"); if (!ladish_graph_get_connection_ports(graph_handle, connection_id, &port1, &port2)) { log_error("cannot find ports that are disconnect-requested"); ASSERT_NO_PASS; return false; } if (graph_handle == g_studio.studio_graph) { port1_id = ladish_port_get_jack_id(port1); port2_id = ladish_port_get_jack_id(port2); } else { port1_id = ladish_port_get_jack_id_room(port1); port2_id = ladish_port_get_jack_id_room(port2); } return graph_proxy_disconnect_ports(virtualizer_ptr->jack_graph_proxy, port1_id, port2_id); } static void ports_connected(void * context, uint64_t client1_id, uint64_t port1_id, uint64_t client2_id, uint64_t port2_id) { ladish_port_handle port1; ladish_port_handle port2; uint64_t connection_id; ladish_graph_handle vgraph1; ladish_graph_handle vgraph2; log_info("ports_connected %"PRIu64":%"PRIu64" %"PRIu64":%"PRIu64"", client1_id, port1_id, client2_id, port2_id); if (!lookup_port(virtualizer_ptr, port1_id, &port1, &vgraph1)) { return; } if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2)) { return; } if (vgraph1 != vgraph2) { /* TODO */ log_error("ignoring connection with endpoints in different vgraphs"); return; } ladish_graph_add_connection(virtualizer_ptr->jack_graph, port1, port2, false); if (ladish_graph_find_connection(vgraph1, port1, port2, &connection_id)) { log_info("showing hidden virtual connection"); ladish_graph_show_connection(vgraph1, connection_id); } else { log_info("creating new virtual connection"); ladish_graph_add_connection(vgraph1, port1, port2, false); } } static void ports_disconnected(void * context, uint64_t client1_id, uint64_t port1_id, uint64_t client2_id, uint64_t port2_id) { ladish_port_handle port1; ladish_port_handle port2; uint64_t connection_id; ladish_graph_handle vgraph1; ladish_graph_handle vgraph2; log_info("ports_disconnected %"PRIu64":%"PRIu64" %"PRIu64":%"PRIu64"", client1_id, port1_id, client2_id, port2_id); if (!lookup_port(virtualizer_ptr, port1_id, &port1, &vgraph1)) { return; } if (!lookup_port(virtualizer_ptr, port2_id, &port2, &vgraph2)) { return; } if (vgraph1 != vgraph2) { /* TODO */ log_error("ignoring connection with endpoints in different vgraphs"); return; } if (ladish_graph_find_connection(virtualizer_ptr->jack_graph, port1, port2, &connection_id)) { ladish_graph_remove_connection(virtualizer_ptr->jack_graph, connection_id, true); } else { log_error("ports %"PRIu64":%"PRIu64" and %"PRIu64":%"PRIu64" are not connected in the JACK graph", client1_id, port1_id, client2_id, port2_id); } if (ladish_graph_find_connection(vgraph1, port1, port2, &connection_id)) { ladish_graph_remove_connection(vgraph1, connection_id, false); } else { log_error("ports %"PRIu64":%"PRIu64" and %"PRIu64":%"PRIu64" are not connected in the virtual graph", client1_id, port1_id, client2_id, port2_id); } } #undef virtualizer_ptr bool ladish_virtualizer_create( graph_proxy_handle jack_graph_proxy, ladish_graph_handle jack_graph, ladish_virtualizer_handle * handle_ptr) { struct virtualizer * virtualizer_ptr; virtualizer_ptr = malloc(sizeof(struct virtualizer)); if (virtualizer_ptr == NULL) { log_error("malloc() failed for struct virtualizer"); return false; } virtualizer_ptr->jack_graph_proxy = jack_graph_proxy; virtualizer_ptr->jack_graph = jack_graph; virtualizer_ptr->system_client_id = 0; virtualizer_ptr->our_clients_count = 0; if (!graph_proxy_attach( jack_graph_proxy, virtualizer_ptr, clear, client_appeared, NULL, /* jackdbus does not have client rename functionality (yet) */ client_disappeared, port_appeared, port_renamed, port_disappeared, ports_connected, ports_disconnected)) { free(virtualizer_ptr); return false; } *handle_ptr = (ladish_virtualizer_handle)virtualizer_ptr; return true; } #define virtualizer_ptr ((struct virtualizer *)handle) void ladish_virtualizer_set_graph_connection_handlers( ladish_virtualizer_handle handle, ladish_graph_handle graph) { ladish_graph_set_connection_handlers(graph, virtualizer_ptr, ports_connect_request, ports_disconnect_request); } unsigned int ladish_virtualizer_get_our_clients_count( ladish_virtualizer_handle handle) { return virtualizer_ptr->our_clients_count; } static bool app_has_a2j_ports(ladish_graph_handle jack_graph, const uuid_t app_uuid) { ladish_client_handle a2jclient; a2jclient = ladish_graph_find_client_by_uuid(jack_graph, g_a2j_uuid); if (a2jclient == NULL) { return false; } return ladish_graph_client_has_visible_app_port(jack_graph, a2jclient, app_uuid); } bool ladish_virtualizer_is_hidden_app( ladish_graph_handle jack_graph, const uuid_t app_uuid, const char * app_name) { ladish_client_handle jclient; ladish_graph_handle vgraph; uuid_t vclient_uuid; ladish_client_handle vclient; //ladish_graph_dump(g_studio.jack_graph); if (app_has_a2j_ports(jack_graph, app_uuid)) { log_info("app '%s' still has a2j ports", app_name); return false; } jclient = ladish_graph_find_client_by_app(jack_graph, app_uuid); if (jclient == NULL) { log_info("App without JACK client is treated as hidden one"); return true; } ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */ vgraph = ladish_client_get_vgraph(jclient); if (vgraph == NULL) { ASSERT_NO_PASS; return true; } //ladish_graph_dump(vgraph); if (!ladish_graph_client_looks_empty(jack_graph, jclient) || !ladish_graph_client_is_hidden(jack_graph, jclient)) { return false; } if (!ladish_client_get_interlink(jclient, vclient_uuid)) { if (ladish_graph_client_is_empty(jack_graph, jclient)) { log_info("jack client of app '%s' has no interlinked vgraph client and no ports", app_name); } else { log_error("jack client of app '%s' has no interlinked vgraph client", app_name); ASSERT_NO_PASS; } return true; } vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid); if (vclient == NULL) { ASSERT_NO_PASS; return true; } if (!ladish_graph_client_looks_empty(vgraph, vclient)) { return false; } ASSERT(ladish_graph_client_is_hidden(vgraph, vclient)); /* vclients are automatically hidden when they start looking empty (on port disappear) */ return true; } struct app_remove_context { uuid_t app_uuid; const char * app_name; }; #define app_info_ptr ((struct app_remove_context *)context) static bool remove_app_port( void * context, ladish_graph_handle graph_handle, bool UNUSED(hidden), void * UNUSED(client_iteration_context_ptr), ladish_client_handle UNUSED(client_handle), const char * client_name, ladish_port_handle port_handle, const char * port_name, uint32_t port_type, uint32_t port_flags) { ladish_graph_handle vgraph; ladish_client_handle vclient; if (!ladish_port_belongs_to_app(port_handle, app_info_ptr->app_uuid)) { return true; } //log_info("removing port '%s':'%s' (JACK) of app '%s'", client_name, port_name, app_info_ptr->app_name); vgraph = ladish_port_get_vgraph(port_handle); if (vgraph == NULL) { log_error("port '%s':'%s' of app '%s' has no vgraph", client_name, port_name, app_info_ptr->app_name); ASSERT_NO_PASS; return true; } vclient = ladish_graph_get_port_client(vgraph, port_handle); if (vgraph == NULL) { log_error("app port '%s':'%s' not found in vgraph '%s'", client_name, port_name, ladish_graph_get_description(vgraph)); ASSERT_NO_PASS; return true; } log_info( "removing %s %s port %p of app '%s' ('%s':'%s' in %s)", port_type == JACKDBUS_PORT_TYPE_AUDIO ? "audio" : "midi", JACKDBUS_PORT_IS_INPUT(port_flags) ? "input" : "output", port_handle, app_info_ptr->app_name, ladish_graph_get_client_name(vgraph, vclient), ladish_graph_get_port_name(vgraph, port_handle), ladish_graph_get_description(vgraph)); ladish_graph_remove_port(graph_handle, port_handle); ladish_graph_remove_port(vgraph, port_handle); return true; } #undef app_info_ptr void ladish_virtualizer_remove_app( ladish_graph_handle jack_graph, const uuid_t app_uuid, const char * app_name) { ladish_client_handle jclient; ladish_graph_handle vgraph; uuid_t vclient_uuid; ladish_client_handle vclient; bool is_empty; struct app_remove_context ctx; //ladish_graph_dump(g_studio.jack_graph); uuid_copy(ctx.app_uuid, app_uuid); ctx.app_name = app_name; ladish_graph_iterate_nodes(jack_graph, &ctx, NULL, remove_app_port, NULL); jclient = ladish_graph_find_client_by_app(jack_graph, app_uuid); if (jclient == NULL) { log_info("removing app without JACK client"); return; } ASSERT(!ladish_virtualizer_is_a2j_client(jclient)); /* a2j client has no app associated */ vgraph = ladish_client_get_vgraph(jclient); if (vgraph == NULL) { ASSERT_NO_PASS; return; } //ladish_graph_dump(vgraph); /* check whether the client is empty because this cannot be checked later because the client was removed (see where is_empty is used) */ is_empty = ladish_graph_client_is_empty(jack_graph, jclient); ladish_graph_remove_client(jack_graph, jclient); if (!ladish_client_get_interlink(jclient, vclient_uuid)) { if (is_empty) { /* jack client without ports and thus without vgraph client */ return; } log_error("jack client of app '%s' has no interlinked vgraph client", app_name); ladish_graph_dump(g_studio.jack_graph); ladish_graph_dump(vgraph); ASSERT_NO_PASS; return; } vclient = ladish_graph_find_client_by_uuid(vgraph, vclient_uuid); if (vclient == NULL) { ASSERT_NO_PASS; return; } ladish_graph_remove_client(vgraph, vclient); ladish_graph_dump(g_studio.jack_graph); ladish_graph_dump(vgraph); } void ladish_virtualizer_destroy( ladish_virtualizer_handle handle) { log_info("ladish_virtualizer_destroy() called"); graph_proxy_detach((graph_proxy_handle)handle, virtualizer_ptr); free(virtualizer_ptr); } #undef virtualizer_ptr #define vgraph ((ladish_graph_handle)vgraph_context) void ladish_virtualizer_rename_app( void * vgraph_context, const uuid_t uuid, const char * UNUSED(old_name), const char * new_app_name) { ladish_client_handle client; client = ladish_graph_find_client_by_app(vgraph, uuid); if (client != NULL) { ladish_graph_rename_client(vgraph, client, new_app_name); } client = ladish_graph_find_client_by_app(g_studio.jack_graph, uuid); if (client != NULL) { ladish_graph_rename_client(g_studio.jack_graph, client, new_app_name); } } #undef vgraph bool ladish_virtualizer_is_system_client( uuid_t uuid) { if (uuid_compare(uuid, g_system_capture_uuid) == 0) { return true; } if (uuid_compare(uuid, g_system_playback_uuid) == 0) { return true; } return false; } bool ladish_virtualizer_is_a2j_client(ladish_client_handle jclient) { uuid_t jclient_uuid; ladish_client_get_uuid(jclient, jclient_uuid); return uuid_compare(jclient_uuid, g_a2j_uuid) == 0; } static bool move_capture_port_callback( void * context, ladish_graph_handle graph_handle, bool UNUSED(hidden), ladish_client_handle client_handle, const char * UNUSED(client_name), ladish_port_handle port_handle, const char * UNUSED(port_name), uint32_t UNUSED(port_type), uint32_t port_flags) { ASSERT(client_handle != context); /* source and destination clients must be differ */ if (JACKDBUS_PORT_IS_INPUT(port_flags)) { ladish_graph_move_port(graph_handle, port_handle, context); } return true; } bool ladish_virtualizer_split_client(ladish_graph_handle vgraph, uint64_t client_id) { ladish_client_handle vclient1; ladish_client_handle vclient2; const char * name; vclient1 = ladish_graph_find_client_by_id(vgraph, client_id); if (vclient1 == NULL) { log_error("Cannot find client %"PRIu64" in %s", client_id, ladish_graph_get_description(vgraph)); return false; } name = ladish_graph_get_client_name(vgraph, vclient1); if (!ladish_client_create(NULL, &vclient2)) { log_error("ladish_client_create() failed."); return false; } ladish_client_interlink_copy(vclient2, vclient1); ladish_client_copy_app(vclient2, vclient1); if (!ladish_graph_add_client(vgraph, vclient2, name, false)) { log_error("ladish_graph_add_client() failed to add client '%s' to virtual graph", name); ladish_client_destroy(vclient2); return false; } return ladish_graph_interate_client_ports(vgraph, vclient1, vclient2, move_capture_port_callback); } static bool move_port_callback( void * context, ladish_graph_handle graph_handle, bool UNUSED(hidden), ladish_client_handle client_handle, const char * UNUSED(client_name), ladish_port_handle port_handle, const char * UNUSED(port_name), uint32_t UNUSED(port_type), uint32_t UNUSED(port_flags)) { ASSERT(client_handle != context); /* source and destination clients must be differ */ ladish_graph_move_port(graph_handle, port_handle, context); return true; } bool ladish_virtualizer_join_clients( ladish_graph_handle vgraph, uint64_t client1_id, uint64_t client2_id) { ladish_client_handle vclient1; ladish_client_handle vclient2; if (client1_id == client2_id) { log_error("Cannot join same client"); return false; } vclient1 = ladish_graph_find_client_by_id(vgraph, client1_id); if (vclient1 == NULL) { log_error("Cannot find client %"PRIu64" in %s", client1_id, ladish_graph_get_description(vgraph)); return false; } vclient2 = ladish_graph_find_client_by_id(vgraph, client2_id); if (vclient2 == NULL) { log_error("Cannot find client %"PRIu64" in %s", client2_id, ladish_graph_get_description(vgraph)); return false; } ladish_graph_interate_client_ports(vgraph, vclient2, vclient1, move_port_callback); ladish_graph_remove_client(vgraph, vclient2); return true; }