ladish/daemon/jackdbus_mgr.c

1482 lines
43 KiB
C

/* -*- Mode: C ; indent-tabs-mode: t ; tab-width: 8 ; c-basic-offset: 8 -*- */
/*
* LASH
*
* Copyright (C) 2008,2009 Nedko Arnaudov <nedko@arnaudov.name>
* Copyright (C) 2008 Juuso Alasuutari <juuso.alasuutari@gmail.com>
* Copyright (C) 2002 Robert Ham <rah@bash.sh>
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string.h>
#include <dbus/dbus.h>
#include "../common/safety.h"
#include "../common/debug.h"
#include "jackdbus_mgr.h"
#include "jack_patch.h"
#include "jack_mgr_client.h"
#include "server.h"
#include "client.h"
#include "project.h"
#define JACKDBUS_SERVICE "org.jackaudio.service"
#define JACKDBUS_OBJECT "/org/jackaudio/Controller"
#define JACKDBUS_IFACE_CONTROL "org.jackaudio.JackControl"
#define JACKDBUS_IFACE_PATCHBAY "org.jackaudio.JackPatchbay"
static lashd_jackdbus_mgr_t *g_jack_mgr_ptr = NULL;
static DBusHandlerResult
lashd_jackdbus_handler(DBusConnection *connection,
DBusMessage *message,
void *data);
static void
lashd_jackdbus_mgr_create_raw_clients(lashd_jackdbus_mgr_t *mgr);
static bool
lashd_jackdbus_mgr_get_client_data(jack_mgr_client_t *client);
static
void
lashd_jackdbus_mgr_is_server_started_return_handler(
DBusPendingCall * pending,
void * data)
{
DBusMessage * msg;
const char * err_str;
DBusError err;
dbus_bool_t is_started;
msg = dbus_pending_call_steal_reply(pending);
if (msg == NULL)
{
lash_error("Cannot get method return from pending call");
goto unref_pending;
}
if (!method_return_verify(msg, &err_str))
{
lash_error("Failed to check whether JACK server is started: %s", err_str);
goto unref_msg;
}
dbus_error_init(&err);
if (!dbus_message_get_args(
msg,
&err,
DBUS_TYPE_BOOLEAN, &is_started,
DBUS_TYPE_INVALID))
{
lash_error("Cannot get message argument: %s", err.message);
dbus_error_free(&err);
goto unref_msg;
}
*(bool *)data = is_started ? true : false;
unref_msg:
dbus_message_unref(msg);
unref_pending:
dbus_pending_call_unref(pending);
}
static
bool
lashd_jackdbus_mgr_is_server_started()
{
bool is_started;
is_started = false;
if (!method_call_new_void(
g_server->dbus_service,
&is_started,
lashd_jackdbus_mgr_is_server_started_return_handler,
true,
JACKDBUS_SERVICE,
JACKDBUS_OBJECT,
JACKDBUS_IFACE_CONTROL,
"IsStarted"))
{
lash_error("method_call_new_void() failed for IsStarted");
return false;
}
return is_started;
}
lashd_jackdbus_mgr_t *
lashd_jackdbus_mgr_new(void)
{
if (!g_server || !g_server->dbus_service
|| !g_server->dbus_service->connection) {
lash_error("NULL server pointer or no D-Bus connection");
return NULL;
}
lashd_jackdbus_mgr_t *mgr;
DBusError err;
if (!(mgr = lash_calloc(1, sizeof(lashd_jackdbus_mgr_t)))) {
lash_error("Failed to allocate memory for "
"lashd_jackdbus_mgr_t");
return NULL;
}
dbus_error_init(&err);
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_PATCHBAY "'"
",member='ClientAppeared'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_PATCHBAY "'"
",member='ClientDisappeared'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_PATCHBAY "'"
",member='PortAppeared'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_PATCHBAY "'"
",member='PortsConnected'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_PATCHBAY "'"
",member='PortsDisconnected'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(
g_server->dbus_service->connection,
"type='signal',interface='" DBUS_INTERFACE_DBUS "',member=NameOwnerChanged,arg0='" JACKDBUS_SERVICE "'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_CONTROL "'"
",member='ServerStarted'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
dbus_bus_add_match(g_server->dbus_service->connection,
"type='signal'"
",sender='" JACKDBUS_SERVICE "'"
",path='" JACKDBUS_OBJECT "'"
",interface='" JACKDBUS_IFACE_CONTROL "'"
",member='ServerStopped'",
&err);
if (dbus_error_is_set(&err)) {
lash_error("Failed to add D-Bus match rule: %s", err.message);
dbus_error_free(&err);
goto fail;
}
if (!dbus_connection_add_filter(g_server->dbus_service->connection,
lashd_jackdbus_handler,
NULL, NULL)) {
lash_error("Failed to add D-Bus filter");
goto fail;
}
INIT_LIST_HEAD(&mgr->clients);
g_jack_mgr_ptr = mgr;
/* Get list of unknown JACK clients */
if (lashd_jackdbus_mgr_is_server_started())
{
lashd_jackdbus_mgr_get_graph(mgr);
lashd_jackdbus_mgr_create_raw_clients(mgr);
}
return mgr;
fail:
free(mgr);
return NULL;
}
static void
lashd_jackdbus_mgr_graph_free(void)
{
g_jack_mgr_ptr->graph_version = 0;
if (g_jack_mgr_ptr->graph) {
dbus_message_unref(g_jack_mgr_ptr->graph);
g_jack_mgr_ptr->graph = NULL;
}
}
/** Return handler for GetClientPid method call in @ref lashd_jackdbus_get_client_pid.
* @param pending Pointer to D-Bus pending call.
* @param data Pointer to memory location in which to store the PID.
*/
static void
lashd_jackdbus_get_client_pid_return_handler(DBusPendingCall *pending,
void *data)
{
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
if (msg) {
const char *err_str;
if (!method_return_verify(msg, &err_str))
lash_error("Failed to get client pid: %s", err_str);
else {
DBusError err;
dbus_error_init(&err);
if (!dbus_message_get_args(msg, &err,
DBUS_TYPE_INT64, data,
DBUS_TYPE_INVALID)) {
lash_error("Cannot get message argument: %s", err.message);
dbus_error_free(&err);
}
}
dbus_message_unref(msg);
} else
lash_error("Cannot get method return from pending call");
dbus_pending_call_unref(pending);
}
/** Get the PID of the JACK client whose JACK ID matches @a client_id.
* @param client_id ID of JACK client whose PID to get.
* @return PID of JACK client whose ID matches @a client_id, or 0 if no such
* client exists.
*/
static dbus_int64_t
lashd_jackdbus_get_client_pid(dbus_uint64_t client_id)
{
dbus_int64_t pid;
pid = 0;
if (!method_call_new_valist(g_server->dbus_service, &pid,
lashd_jackdbus_get_client_pid_return_handler,
true,
JACKDBUS_SERVICE,
JACKDBUS_OBJECT,
JACKDBUS_IFACE_PATCHBAY,
"GetClientPID",
DBUS_TYPE_UINT64, &client_id,
DBUS_TYPE_INVALID))
pid = 0;
return pid;
}
static
void
lashd_jackdbus_on_client_appeared(
const char * client_name,
dbus_uint64_t client_id)
{
dbus_int64_t pid;
struct lash_client * lash_client_ptr;
project_t * project_ptr;
jack_mgr_client_t * jack_client_ptr;
pid = lashd_jackdbus_get_client_pid(client_id);
if (pid == 0)
{
lash_info(
"Ignoring new JACK client '%s' with id %llu, pid %lld",
client_name,
(unsigned long long)client_id,
(long long)pid);
return;
}
lash_info("New JACK client '%s' with id %llu, pid %lld",
client_name, (unsigned long long)client_id, (long long)pid);
jack_client_ptr = jack_mgr_client_new();
jack_client_ptr->name = lash_strdup(client_name);
jack_client_ptr->jackdbus_id = client_id;
jack_client_ptr->pid = (pid_t) pid;
/* Add JACK client to the active client list */
list_add_tail(&jack_client_ptr->siblings, &g_jack_mgr_ptr->clients);
lash_client_ptr = server_find_client_by_pid(pid);
if (lash_client_ptr == NULL)
{
lash_client_ptr = server_find_lost_client_by_pid(pid);
if (lash_client_ptr != NULL)
{
lash_info("Found launched client '%s' with pid %lld", lash_client_ptr->name, (long long)pid);
lash_client_ptr->flags |= LASH_Restored;
client_resume_project(lash_client_ptr);
}
}
if (lash_client_ptr == NULL)
{
/* None of the known LASH clients have the same PID */
/* create new raw (liblash-less) client object */
lash_client_ptr = client_new();
if (!client_fill_by_pid(lash_client_ptr, pid))
{
client_destroy(lash_client_ptr);
return;
}
lash_strset(&lash_client_ptr->class, client_name);
project_ptr = server_get_newborn_project();
project_new_client(project_ptr, lash_client_ptr);
lash_debug("New raw client of project '%s' for JACK client '%s'", project_ptr->name, client_name);
}
/* Copy UUID and move JACK patches from LASH client to JACK client */
uuid_copy(jack_client_ptr->id, lash_client_ptr->id);
jack_mgr_client_free_patch_list(&jack_client_ptr->old_patches);
INIT_LIST_HEAD(&jack_client_ptr->old_patches);
list_splice_init(&lash_client_ptr->jack_patches, &jack_client_ptr->old_patches);
jack_mgr_client_dup_patch_list(&jack_client_ptr->old_patches, &jack_client_ptr->backup_patches);
/* Extract JACK client's data from latest graph */
lashd_jackdbus_mgr_get_graph(g_jack_mgr_ptr);
if (!lashd_jackdbus_mgr_get_client_data(jack_client_ptr))
lash_error("Problem extracting client data from graph");
/* Copy JACK client name to LASH client, make sure it has a class string */
lash_strset(&lash_client_ptr->jack_client_name, jack_client_ptr->name);
}
/** Fill @a mgr 's unknown_clients list with one @a jack_mgr_client_t object per
* each currently running JACK client. This is done in @a lashd_jackdbus_mgr_new
* for @a mgr to be able to later on match JACK client PIDs against LASH clients.
* @a mgr must be properly initialized, contain a valid graph, and its
* unknown_clients list must be empty.
* @param mgr Pointer to JACK D-Bus manager.
*/
static void
lashd_jackdbus_mgr_create_raw_clients(lashd_jackdbus_mgr_t *mgr)
{
lash_debug("Creating raw clients from JACK graph");
DBusMessageIter iter, array_iter, struct_iter;
dbus_uint64_t client_id;
const char *client_name;
if (!mgr->graph) {
lash_error("Cannot find graph");
return;
}
/* The graph message has already been checked, this should be safe */
dbus_message_iter_init(mgr->graph, &iter);
/* Skip over the graph version argument */
dbus_message_iter_next(&iter);
/* Check that we're getting the client array as expected */
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
lash_error("Cannot find client array in graph");
goto fail;
}
/* Iterate the client array and store clients as unknown */
dbus_message_iter_recurse(&iter, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
dbus_message_iter_recurse(&array_iter, &struct_iter);
/* Get client's data from array */
if (!method_iter_get_args(&struct_iter,
DBUS_TYPE_UINT64, &client_id,
DBUS_TYPE_STRING, &client_name,
DBUS_TYPE_INVALID)) {
lash_error("Failed to parse client array in graph");
goto fail;
}
lashd_jackdbus_on_client_appeared(client_name, client_id);
dbus_message_iter_next(&array_iter);
}
return;
fail:
/* If we're here there was something rotten in the graph message,
we should remove it */
lashd_jackdbus_mgr_graph_free();
}
void
lashd_jackdbus_mgr_clear(
void)
{
struct list_head * node;
struct list_head * next;
list_for_each_safe (node, next, &g_jack_mgr_ptr->clients)
{
jack_mgr_client_destroy(list_entry(node, jack_mgr_client_t, siblings));
}
lashd_jackdbus_mgr_graph_free();
}
void
lashd_jackdbus_mgr_destroy(lashd_jackdbus_mgr_t *mgr)
{
if (mgr)
{
lashd_jackdbus_mgr_clear();
free(mgr);
g_jack_mgr_ptr = NULL;
}
}
static void
lashd_jackdbus_connect_return_handler(DBusPendingCall *pending,
void *data)
{
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
if (msg) {
const char *err_str;
if (!method_return_verify(msg, &err_str))
lash_error("Failed to connect ports: %s", err_str);
else
lash_debug("Ports connected");
dbus_message_unref(msg);
} else
lash_error("Cannot get method return from pending call");
dbus_pending_call_unref(pending);
}
static void
lashd_jackdbus_mgr_connect_ports(const char *client1_name,
const char *port1_name,
const char *client2_name,
const char *port2_name)
{
lash_info("Attempting to resume patch '%s:%s' -> '%s:%s'",
client1_name, port1_name, client2_name, port2_name);
if (!client1_name[0] ||
!client2_name[0] ||
!port1_name[0] ||
!port2_name[0])
{
lash_error("Ignoring patch '%s:%s' -> '%s:%s'", client1_name, port1_name, client2_name, port2_name);
return;
}
/* Send a port connect request */
method_call_new_valist(g_server->dbus_service,
NULL,
lashd_jackdbus_connect_return_handler,
false,
JACKDBUS_SERVICE,
JACKDBUS_OBJECT,
JACKDBUS_IFACE_PATCHBAY,
"ConnectPortsByName",
DBUS_TYPE_STRING,
&client1_name,
DBUS_TYPE_STRING,
&port1_name,
DBUS_TYPE_STRING,
&client2_name,
DBUS_TYPE_STRING,
&port2_name,
DBUS_TYPE_INVALID);
}
/** Remove the unknown client whose id parameter matches \a client_id.
* @param client_id The disappeared client's JACK D-Bus ID.
*/
static void
lashd_jackdbus_on_client_disappeared(dbus_uint64_t client_id)
{
jack_mgr_client_t * jack_client_ptr;
struct lash_client * lash_client_ptr;
jack_client_ptr = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients, client_id);
if (jack_client_ptr == NULL)
{
return;
}
lash_client_ptr = server_find_client_by_id(jack_client_ptr->id);
if (lash_client_ptr != NULL &&
lash_client_ptr->dbus_name == NULL)
{
client_disconnected(lash_client_ptr);
lash_debug(
"Closing raw client '%s' of project '%s' because JACK client '%s' disappeared",
lash_client_ptr->name,
lash_client_ptr->project->name,
jack_client_ptr->name);
return;
}
lash_debug("Removing JACK client '%s'", jack_client_ptr->name);
list_del(&jack_client_ptr->siblings);
jack_mgr_client_destroy(jack_client_ptr);
}
/** Search all known JACK clients for old patches that involve the foreign port
* described by @a client_name and @a port_name, attempt to reconnect all found
* patches.
* @param client_name Name of the client that the foreign port belongs to.
* @param port_name Name of the foreign port.
*/
static void
lashd_jackdbus_mgr_new_foreign_port(const char *client_name,
const char *port_name)
{
lash_debug("New foreign port '%s:%s'", client_name, port_name);
struct list_head *node, *node2;
jack_mgr_client_t *client;
jack_patch_t *patch;
/* Iterate JACK clients */
list_for_each (node, &g_jack_mgr_ptr->clients) {
client = list_entry(node, jack_mgr_client_t, siblings);
/* Iterate client's old patches */
list_for_each (node2, &client->old_patches) {
patch = list_entry(node2, jack_patch_t, siblings);
/* If patch is a connection between client and new port
reconnect it now */
if (uuid_compare(patch->src_client_id, client->id) == 0) {
if (!patch->dest_client
|| strcmp(patch->dest_client, client_name) != 0
|| strcmp(patch->dest_port, port_name) != 0)
continue;
lashd_jackdbus_mgr_connect_ports(client->name,
patch->src_port,
client_name,
port_name);
} else {
if (!patch->src_client
|| strcmp(patch->src_client, client_name) != 0
|| strcmp(patch->src_port, port_name) != 0)
continue;
lashd_jackdbus_mgr_connect_ports(client_name,
port_name,
client->name,
patch->dest_port);
}
}
}
}
static void
lashd_jackdbus_mgr_new_client_port(jack_mgr_client_t *client,
const char *client_name,
const char *port_name)
{
lash_debug("New client port '%s:%s'", client_name, port_name);
struct list_head *node;
jack_patch_t *patch;
const char *ptr;
jack_mgr_client_t *c;
/* Iterate the client's old patches and try to connect those which
contain the new port */
list_for_each (node, &client->old_patches) {
patch = list_entry(node, jack_patch_t, siblings);
if (uuid_compare(patch->src_client_id, client->id) == 0
&& strcmp(patch->src_port, port_name) == 0) {
ptr = (patch->dest_client
? patch->dest_client
: ((c = jack_mgr_client_find_by_id(&g_jack_mgr_ptr->clients,
patch->dest_client_id))
? (c->name ? c->name : "")
: ""));
lashd_jackdbus_mgr_connect_ports(client_name, port_name,
ptr, patch->dest_port);
} else if (uuid_compare(patch->dest_client_id, client->id) == 0
&& strcmp(patch->dest_port, port_name) == 0) {
ptr = (patch->src_client
? patch->src_client
: ((c = jack_mgr_client_find_by_id(&g_jack_mgr_ptr->clients,
patch->src_client_id))
? (c->name ? c->name : "")
: ""));
lashd_jackdbus_mgr_connect_ports(ptr, patch->src_port,
client_name, port_name);
}
}
}
static void
lashd_jackdbus_mgr_del_old_patch(jack_mgr_client_t *client,
dbus_uint64_t src_id,
const char *src_name,
const char *src_port,
dbus_uint64_t dest_id,
const char *dest_name,
const char *dest_port);
static void
lashd_jackdbus_mgr_ports_connected(dbus_uint64_t client1_id,
const char *client1_name,
const char *port1_name,
dbus_uint64_t client2_id,
const char *client2_name,
const char *port2_name)
{
jack_mgr_client_t *client;
client = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients,
client1_id);
if (client)
{
lashd_jackdbus_mgr_del_old_patch(client, client1_id,
client1_name, port1_name,
client2_id, client2_name,
port2_name);
jack_mgr_client_modified(client);
/* If this is an internal connection we're done */
if (client1_id == client2_id)
return;
}
client = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients,
client2_id);
if (client)
{
lashd_jackdbus_mgr_del_old_patch(client, client1_id,
client1_name, port1_name,
client2_id, client2_name,
port2_name);
jack_mgr_client_modified(client);
}
}
static void
lashd_jackdbus_mgr_ports_disconnected(
dbus_uint64_t client1_id,
const char * client1_name,
const char * port1_name,
dbus_uint64_t client2_id,
const char * client2_name,
const char * port2_name)
{
jack_mgr_client_t *client;
client = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients, client1_id);
if (client)
{
jack_mgr_client_modified(client);
/* If this was an internal connection we're done */
if (client1_id == client2_id)
return;
}
client = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients, client2_id);
if (client)
{
jack_mgr_client_modified(client);
}
}
static
void
lashd_jackdbus_handle_patchbay_signal(
DBusMessage * message_ptr)
{
const char * signal_name;
DBusError err;
const char *client1_name, *port1_name;
dbus_uint64_t dummy, client1_id;
jack_mgr_client_t *client;
const char *client2_name, *port2_name;
dbus_uint64_t client2_id;
signal_name = dbus_message_get_member(message_ptr);
if (signal_name == NULL)
{
lash_error("Received JACK signal with NULL member");
return;
}
dbus_error_init(&err);
if (strcmp(signal_name, "ClientAppeared") == 0)
{
lash_debug("Received ClientAppeared signal");
if (!dbus_message_get_args(
message_ptr,
&err,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_INVALID))
{
goto fail;
}
lashd_jackdbus_on_client_appeared(client1_name, client1_id);
return;
}
if (strcmp(signal_name, "ClientDisappeared") == 0) {
lash_debug("Received ClientDisappeared signal");
if (!dbus_message_get_args(message_ptr, &err,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_INVALID))
goto fail;
lashd_jackdbus_on_client_disappeared(client1_id);
return;
}
if (strcmp(signal_name, "PortAppeared") == 0)
{
lash_debug("Received PortAppeared signal");
if (!dbus_message_get_args(
message_ptr,
&err,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_INVALID))
{
goto fail;
}
/* Check if the new port belongs to a known client */
client = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients, client1_id);
if (client)
{
if (!list_empty(&client->old_patches))
lashd_jackdbus_mgr_new_client_port(client, client1_name, port1_name);
}
else
{
lashd_jackdbus_mgr_new_foreign_port(client1_name, port1_name);
}
return;
}
if (strcmp(signal_name, "PortsConnected") == 0)
{
lash_debug("Received PortsConnected signal");
if (!dbus_message_get_args(
message_ptr,
&err,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_UINT64, &client2_id,
DBUS_TYPE_STRING, &client2_name,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_STRING, &port2_name,
DBUS_TYPE_INVALID))
{
goto fail;
}
lashd_jackdbus_mgr_ports_connected(
client1_id,
client1_name,
port1_name,
client2_id,
client2_name,
port2_name);
return;
}
if (strcmp(signal_name, "PortsDisconnected") == 0)
{
lash_debug("Received PortsDisconnected signal");
if (!dbus_message_get_args(
message_ptr,
&err,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_UINT64, &client2_id,
DBUS_TYPE_STRING, &client2_name,
DBUS_TYPE_UINT64, &dummy,
DBUS_TYPE_STRING, &port2_name,
DBUS_TYPE_INVALID))
{
goto fail;
}
lashd_jackdbus_mgr_ports_disconnected(
client1_id,
client1_name,
port1_name,
client2_id,
client2_name,
port2_name);
return;
}
return;
fail:
lash_error("Cannot get message arguments: %s", err.message);
dbus_error_free(&err);
}
static
void
lashd_jackdbus_handle_control_signal(
DBusMessage * message_ptr)
{
const char * signal_name;
signal_name = dbus_message_get_member(message_ptr);
if (signal_name == NULL)
{
lash_error("Received JACK signal with NULL member");
return;
}
if (strcmp(signal_name, "ServerStarted") == 0)
{
lash_info("JACK server start detected.");
g_jack_mgr_ptr->graph_version = 0;
lashd_jackdbus_mgr_get_graph(g_jack_mgr_ptr);
lashd_jackdbus_mgr_create_raw_clients(g_jack_mgr_ptr);
return;
}
if (strcmp(signal_name, "ServerStopped") == 0)
{
lash_info("JACK server stop detected.");
lashd_jackdbus_mgr_clear();
return;
}
}
static
DBusHandlerResult
lashd_jackdbus_handle_bus_signal(
DBusMessage * message_ptr)
{
const char * signal_name;
DBusError err;
const char * object_name;
const char * old_owner;
const char * new_owner;
signal_name = dbus_message_get_member(message_ptr);
if (signal_name == NULL)
{
lash_error("Received bus signal with NULL member");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
//lash_info("bus signal '%s' received", signal_name);
dbus_error_init(&err);
if (strcmp(signal_name, "NameOwnerChanged") == 0)
{
//lash_info("NameOwnerChanged signal received");
if (!dbus_message_get_args(
message_ptr,
&err,
DBUS_TYPE_STRING, &object_name,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID)) {
lash_error("Cannot get message arguments: %s", err.message);
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (strcmp(object_name, JACKDBUS_SERVICE) != 0)
{
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (old_owner[0] == '\0')
{
lash_info("JACK serivce appeared");
g_jack_mgr_ptr->graph_version = 0;
}
else if (new_owner[0] == '\0')
{
lash_info("JACK serivce disappeared");
g_jack_mgr_ptr->graph_version = 0;
}
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/** This is jackdbus_mgr's D-Bus message handler. It intercepts signals from
* jackdbus and the session bus and calls the appropriate handlers. Messages
* not relevant to jackdbus pass through untouched.
* @param connection The D-Bus connection.
* @param message The intercepted message.
* @param data User data.
* @return Whether the message was handled or not.
*
*/
static DBusHandlerResult
lashd_jackdbus_handler(DBusConnection *connection,
DBusMessage *message,
void *data)
{
const char *path, *interface;
/* Let non-signal messages and signals with no interface pass through */
if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL
|| !(interface = dbus_message_get_interface(message)))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
/* Handle JACK patchbay and control interface signals */
if ((path = dbus_message_get_path(message))
&& strcmp(path, JACKDBUS_OBJECT) == 0) {
if (strcmp(interface, JACKDBUS_IFACE_PATCHBAY) == 0) {
lashd_jackdbus_handle_patchbay_signal(message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (strcmp(interface, JACKDBUS_IFACE_CONTROL) == 0) {
lashd_jackdbus_handle_control_signal(message);
return DBUS_HANDLER_RESULT_HANDLED;
}
}
/* Handle session bus signals to track JACK service alive state */
if (strcmp(interface, DBUS_INTERFACE_DBUS) == 0)
return lashd_jackdbus_handle_bus_signal(message);
/* Let everything else pass through */
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/** If the JACK patch between clients with IDs @a src_id and @a dest_id belongs
* to @a client fill in @a src_uuid and @a dest_uuid.
* @param client Pointer to client.
* @param src_id Patch source client ID.
* @param dest_id Patch destination client ID.
* @param src_uuid Pointer to pointer to source UUID.
* @param dest_uuid Pointer to pointer to destination UUID.
* @return False if the patch doesn't belong to @a client, true otherwise.
*/
static bool
lashd_jackdbus_mgr_get_patch_uuids(jack_mgr_client_t *client,
dbus_uint64_t src_id,
dbus_uint64_t dest_id,
uuid_t **src_uuid,
uuid_t **dest_uuid)
{
jack_mgr_client_t *c;
/* Check if the described patch belongs to the client */
if (src_id != client->jackdbus_id && dest_id != client->jackdbus_id)
return false;
/* Grab pointer to UUID of source client if it is known */
*src_uuid = ((src_id == client->jackdbus_id)
? &client->id
: ((c = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients,
src_id))
? &c->id
: NULL));
/* Grab pointer to UUID of destination client if it is known */
*dest_uuid = ((dest_id == client->jackdbus_id)
? &client->id
: ((c = jack_mgr_client_find_by_jackdbus_id(&g_jack_mgr_ptr->clients,
dest_id))
? &c->id
: NULL));
return true;
}
/** Delete the patch described by @a src_id, @a src_name, @a src_port,
* @a dest_id, @a dest_name, and @a dest_port from @a client 's old_patches
* list.
* @param client Pointer to client.
* @param src_id Patch source client ID.
* @param src_name Patch source client name.
* @param src_port Patch source port name.
* @param dest_id Patch destination client ID.
* @param dest_name Patch destination client name.
* @param dest_port Patch destination port name.
*/
static void
lashd_jackdbus_mgr_del_old_patch(jack_mgr_client_t *client,
dbus_uint64_t src_id,
const char *src_name,
const char *src_port,
dbus_uint64_t dest_id,
const char *dest_name,
const char *dest_port)
{
uuid_t *src_uuid, *dest_uuid;
jack_patch_t *patch;
/* If the patch belongs to the client try to get its pointer */
if (!lashd_jackdbus_mgr_get_patch_uuids(client, src_id, dest_id,
&src_uuid, &dest_uuid)
|| !(patch = jack_patch_find_by_description(&client->old_patches,
src_uuid, src_name, src_port,
dest_uuid, dest_name, dest_port)))
return;
/* Patch was found and can be deleted */
lash_debug("Connected patch '%s:%s' -> '%s:%s' is an old patch "
"of client '%s'; removing from list", src_name,
src_port, dest_name, dest_port, client->name);
list_del(&patch->siblings);
jack_patch_destroy(patch);
#ifdef LASH_DEBUG
if (list_empty(&client->old_patches))
lash_debug("All old patches of client '%s' have "
"now been connected", client->name);
#endif
}
static bool
lashd_jackdbus_mgr_get_client_data(jack_mgr_client_t *client)
{
if (!client) {
lash_error("Client pointer is NULL");
return false;
}
lash_debug("Getting data from graph for client '%s'", client->name);
DBusMessageIter iter, array_iter, struct_iter;
dbus_uint64_t client1_id, client2_id;
const char *client1_name, *port1_name, *client2_name, *port2_name;
if (!g_jack_mgr_ptr->graph) {
lash_error("Cannot find graph");
return false;
}
/* The graph message has already been checked, this should be safe */
dbus_message_iter_init(g_jack_mgr_ptr->graph, &iter);
/* Skip over the graph version argument */
dbus_message_iter_next(&iter);
/* Check that we're getting the client array as expected */
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
lash_error("Cannot find client array in graph");
goto fail;
}
/* Iterate the client array and grab the client's ID */
dbus_message_iter_recurse(&iter, &array_iter);
client->jackdbus_id = 0;
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
dbus_message_iter_recurse(&array_iter, &struct_iter);
if (!method_iter_get_args(&struct_iter,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_INVALID)) {
lash_error("Failed to parse client array in graph");
goto fail;
}
/* See if we found the client */
if (strcmp(client1_name, client->name) == 0) {
lash_debug("Assigning jackdbus ID %llu to client '%s'",
client1_id, client1_name);
client->jackdbus_id = client1_id;
break;
}
dbus_message_iter_next(&array_iter);
}
if (!client->jackdbus_id) {
lash_error("Cannot find client jackdbus ID in graph");
goto fail;
}
if (list_empty(&client->old_patches)) {
lash_debug("Client '%s' has no old patches to check",
client->name);
return true;
}
/* Check that we're getting the port array as expected */
if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_ARRAY) {
lash_error("Cannot find port array for client '%s' in graph",
client1_name);
goto fail;
}
/* Iterate the client's port array and connect pending old_patches */
dbus_message_iter_recurse(&struct_iter, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
dbus_message_iter_recurse(&array_iter, &struct_iter);
if (!method_iter_get_args(&struct_iter,
DBUS_TYPE_UINT64, NULL,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_INVALID)) {
lash_error("Failed to parse port array for client '%s' in graph",
client1_name);
goto fail;
}
lashd_jackdbus_mgr_new_client_port(client, client1_name, port1_name);
dbus_message_iter_next(&array_iter);
}
/* Check that we're getting the patch array as expected */
if (!dbus_message_iter_next(&iter)
|| dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
lash_error("Cannot find patch array in graph");
goto fail;
}
/* Iterate the patch array and grab the new client's patches */
dbus_message_iter_recurse(&iter, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
dbus_message_iter_recurse(&array_iter, &struct_iter);
if (!method_iter_get_args(&struct_iter,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_UINT64, NULL,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_UINT64, &client2_id,
DBUS_TYPE_STRING, &client2_name,
DBUS_TYPE_UINT64, NULL,
DBUS_TYPE_STRING, &port2_name,
DBUS_TYPE_INVALID)) {
lash_error("Failed to parse patch array in graph");
goto fail;
}
lashd_jackdbus_mgr_del_old_patch(client, client1_id,
client1_name, port1_name,
client2_id, client2_name,
port2_name);
dbus_message_iter_next(&array_iter);
}
return true;
fail:
/* If we're here there was something rotten in the graph message,
we should remove it */
lashd_jackdbus_mgr_graph_free();
return false;
}
bool
lashd_jackdbus_mgr_remove_client(lashd_jackdbus_mgr_t *mgr,
uuid_t id,
struct list_head *backup_patches)
{
jack_mgr_client_t *client;
client = jack_mgr_client_find_by_id(&mgr->clients, id);
if (!client) {
lash_error("Unknown client");
return false;
}
lash_debug("Removing client '%s'", client->name);
list_del(&client->siblings);
if (backup_patches)
list_splice_init(&client->backup_patches, backup_patches);
jack_mgr_client_destroy(client);
return true;
}
bool
lashd_jackdbus_mgr_get_client_patches(lashd_jackdbus_mgr_t *mgr,
uuid_t id,
struct list_head *dest)
{
if (!mgr) {
lash_error("JACK manager pointer is NULL");
return false;
}
jack_mgr_client_t *client;
jack_patch_t *patch;
DBusMessageIter iter, array_iter, struct_iter;
dbus_uint64_t client1_id, client2_id;
const char *client1_name, *port1_name, *client2_name, *port2_name;
uuid_t *client1_uuid, *client2_uuid;
if (!mgr->graph) {
lash_error("Cannot find graph");
return false;
}
client = jack_mgr_client_find_by_id(&mgr->clients, id);
if (!client) {
char id_str[37];
uuid_unparse(id, id_str);
lash_error("Cannot find client %s", id_str);
return false;
}
lash_debug("Getting patches for client '%s'", client->name);
/* The graph message has already been checked, this should be safe */
dbus_message_iter_init(mgr->graph, &iter);
/* Skip over the graph version and client array arguments */
dbus_message_iter_next(&iter);
dbus_message_iter_next(&iter);
/* Check that we're getting the patch array as expected */
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
lash_error("Cannot find patch array in graph");
goto fail;
}
/* Iterate the patch array and get the client's patches */
dbus_message_iter_recurse(&iter, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRUCT) {
dbus_message_iter_recurse(&array_iter, &struct_iter);
/* Get the patch description */
if (!method_iter_get_args(&struct_iter,
DBUS_TYPE_UINT64, &client1_id,
DBUS_TYPE_STRING, &client1_name,
DBUS_TYPE_UINT64, NULL,
DBUS_TYPE_STRING, &port1_name,
DBUS_TYPE_UINT64, &client2_id,
DBUS_TYPE_STRING, &client2_name,
DBUS_TYPE_UINT64, NULL,
DBUS_TYPE_STRING, &port2_name,
DBUS_TYPE_INVALID)) {
lash_error("Failed to parse patch array in graph");
goto fail;
}
/* Get patch UUIDs, skip patch if neither is the client's */
if (!lashd_jackdbus_mgr_get_patch_uuids(client, client1_id, client2_id,
&client1_uuid, &client2_uuid)) {
dbus_message_iter_next(&array_iter);
continue;
}
/* Create new patch object and append to the list */
patch = jack_patch_new_with_all(client1_uuid, client2_uuid,
client1_name, client2_name,
port1_name, port2_name);
list_add_tail(&patch->siblings, dest);
dbus_message_iter_next(&array_iter);
}
/* Make a fresh backup of the newly acquired patch list */
jack_mgr_client_free_patch_list(&client->backup_patches);
INIT_LIST_HEAD(&client->backup_patches);
if (!list_empty(dest))
jack_mgr_client_dup_patch_list(dest, &client->backup_patches);
return true;
fail:
/* If we're here there was something rotten in the graph message,
we should remove it */
lashd_jackdbus_mgr_graph_free();
return false;
}
static void
lashd_jackdbus_mgr_graph_return_handler(DBusPendingCall *pending,
void *data)
{
lash_debug("Saving graph");
DBusMessage *msg;
DBusMessageIter iter;
dbus_uint64_t graph_version;
const char *err_str;
msg = dbus_pending_call_steal_reply(pending);
/* Check that the message is valid */
if (!msg) {
lash_error("Cannot get method return from pending call");
goto end;
} else if (!method_return_verify(msg, &err_str)) {
lash_error("Failed to get graph: %s", err_str);
goto end_unref_msg;
} else if (!dbus_message_iter_init(msg, &iter)) {
lash_error("Method return has no arguments");
goto end_unref_msg;
}
/* Check that the graph version argument is valid */
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT64) {
lash_error("Cannot find graph version in graph");
goto end_unref_msg;
}
/* Get the graph version and see if we already have the latest one */
dbus_message_iter_get_basic(&iter, &graph_version);
if (graph_version == g_jack_mgr_ptr->graph_version) {
lash_debug("Current graph is already the latest version");
goto end_unref_msg;
}
/* Free the old graph and save the new one */
lashd_jackdbus_mgr_graph_free();
g_jack_mgr_ptr->graph = msg;
g_jack_mgr_ptr->graph_version = graph_version;
lash_debug("Graph saved");
end:
dbus_pending_call_unref(pending);
return;
end_unref_msg:
dbus_message_unref(msg);
dbus_pending_call_unref(pending);
}
void
lashd_jackdbus_mgr_get_graph(lashd_jackdbus_mgr_t *mgr)
{
if (!mgr) {
lash_error("JACK manager pointer is NULL");
return;
}
lash_debug("Requesting graph version >= %llu", mgr->graph_version);
method_call_new_single(g_server->dbus_service,
NULL,
lashd_jackdbus_mgr_graph_return_handler,
true,
JACKDBUS_SERVICE,
JACKDBUS_OBJECT,
JACKDBUS_IFACE_PATCHBAY,
"GetGraph",
DBUS_TYPE_UINT64,
&mgr->graph_version);
}
/* EOF */